Skip to content

feat(configurator): unified value editor + grouped undo (UX redesign, phase 5) - #695

Merged
jackgranatowski merged 3 commits into
mainfrom
ux/configurator-unified-controls
Aug 20, 2026
Merged

jackgranatowski merged 3 commits into
mainfrom
ux/configurator-unified-controls

Conversation

@jackgranatowski

Copy link
Copy Markdown
Contributor

Phase 5 of the configurator UX redesign — unified controls

Stacked on #691#692#693#694. Merge those first; this branch includes their commits.

Two universal control fixes the audit called out.

1. Grouped undo

A slider drag fires input on every tick, so a single gesture used to push dozens of near-identical snapshots — one Ctrl+Z rewound a single pixel. setOverrides now coalesces a run of consecutive edits to the same token within a short window into one history entry; a different edit or a pause starts a fresh group; undo/redo close the open group. The decision logic (changedKeys, shouldCoalesce) lives in src/lib/history.ts and is unit-tested (8 tests).

2. Unified value editor — Inherit / Value / Expression

ValueField.svelte replaces TokenRow's ad-hoc scale-picker / expand / button editor with three explicit modes:

  • Inherit — no override; shows the framework default and offers a relink to a scale step dropdown (folds in the old sibling-scale picker).
  • Value — a fixed literal you type.
  • Expression — a var()/calc()/clamp()… value shown verbatim.

The key guarantee: an expression override always resolves to Expression mode and is never parsed down to a fallback number, so the next edit can't silently overwrite it — turning it into a fixed value is a deliberate tab click. detectMode / isExpression / splitValueUnit are unit-tested (7 tests).

TokenRow keeps all its phase-1 context (role badge, inherits, used by N, Detached/Invalid warnings) and now edits through ValueField, so All tokens and the Changes panel both gain the unified editor.

Verification

  • npm run check: 0 errors · npm run lint: clean · build OK
  • npm run test: 272/272 (history 8, valueField 7)
  • e2e: shell suite green; screenshot-verified a calc(var(--sf-radius-s) * 2) override renders in Expression mode verbatim with the mode tabs.

Scope / follow-ups

Lands the unified generic editor + grouped undo. Migrating the specialised SliderRow-based panels onto the same three-mode model, and the shared scale control with inline detach diagnostics for radius/border/motion, are follow-ups — the detach detection itself already ships (Changes panel + tokenModel.scaleShadows).

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jackgranatowski, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 864df2e3-171c-411a-bec1-7762f958106c

📥 Commits

Reviewing files that changed from the base of the PR and between d89b7e6 and 27cc308.

📒 Files selected for processing (11)
  • configurator/src/App.svelte
  • configurator/src/components/inputs/TokenRow.svelte
  • configurator/src/components/inputs/ValueField.svelte
  • configurator/src/components/panels/HomePanel.svelte
  • configurator/src/components/shell/SidebarNav.svelte
  • configurator/src/lib/history.ts
  • configurator/src/lib/valueField.ts
  • configurator/tests-components/token-editing.test.js
  • configurator/tests-e2e/shell.spec.js
  • configurator/tests/history.test.ts
  • configurator/tests/valueField.test.ts
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ux/configurator-unified-controls

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a unified Inherit/Value/Expression token editor, time-based grouped undo, a Changes overview, metadata-driven domain classification, and richer token relationship diagnostics.

  • Replaces generic token editing with explicit value modes and scale relinking.
  • Adds token roles, dependency summaries, detached/invalid classifications, and scale-shadow reporting.
  • Consolidates runtime and curation domain routing around domain-map.json.
  • Reorganizes Home and sidebar navigation and introduces the Changes panel.

Confidence Score: 3/5

The PR should not merge until expression mode resynchronizes after external updates and grouped undo preserves separate same-token actions.

Persistent manual editor state can defeat the expression-preservation guarantee, while time-only history grouping makes independently committed states unavailable to undo.

Files Needing Attention: configurator/src/components/inputs/ValueField.svelte, configurator/src/App.svelte, configurator/src/lib/history.ts, configurator/src/components/panels/ChangesPanel.svelte

Important Files Changed

Filename Overview
configurator/src/components/inputs/ValueField.svelte Adds the unified editor, but manual mode persists across external override changes and can misrepresent restored expressions.
configurator/src/App.svelte Integrates grouped undo, but the time/key heuristic merges separate rapid actions on the same token.
configurator/src/lib/history.ts Implements pure coalescing helpers without a gesture or commit-boundary concept.
configurator/src/lib/tokenModel.ts Adds role, dependency, validation-state, change-summary, and scale-shadow modeling with broad unit coverage.
configurator/src/lib/domains.ts Replaces overlapping substring classification with deterministic namespace mapping and exceptions.
configurator/src/components/panels/ChangesPanel.svelte Adds grouped override review and remediation controls, but its invalid-export diagnostic does not match export behavior.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  UI[Token controls] --> Set[setOverrides]
  Set --> History{Same key within 600 ms?}
  History -->|Yes| Merge[Reuse history entry]
  History -->|No| Push[Push previous snapshot]
  Set --> Overrides[Active overrides]
  Overrides --> Editor[ValueField mode detection]
  Overrides --> Changes[Changes classification]
  Overrides --> Preview[Live preview and export]
Loading

Reviews (1): Last reviewed commit: "feat(configurator): unified value editor..." | Re-trigger Greptile

Comment on lines +19 to +20
let manualMode = $state<ValueMode | null>(null);
let mode = $derived<ValueMode>(manualMode ?? detectMode(overrideValue));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Expression mode becomes stale

When a user selects a manual mode and the token's override later changes through undo, redo, theme application, import, or another editor surface, manualMode continues to override detectMode. A restored var() or calc() expression therefore remains displayed as Value and can be overwritten without the deliberate mode switch promised by this editor.

Comment thread configurator/src/App.svelte Outdated
Comment on lines +110 to +117
const keys = changedKeys(prev, next);
const now = Date.now();
// Only push a new undo snapshot when this isn't a continuation of the
// current single-token gesture.
if (!shouldCoalesce(coalesce, keys, now)) {
past = [...past.slice(-49), prev];
}
coalesce = { key: keys.length === 1 ? keys[0] : null, time: now };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Undo merges separate actions

When two independent edits affect the same token within 600 ms, such as committing a value and immediately selecting Inherit, this code treats the second action as part of the first gesture. No snapshot is recorded for the intermediate state, so Ctrl+Z skips an independently committed edit instead of undoing each action separately.

accent: string;
};
const GROUPS: GroupDef[] = [
{ key: "invalid", label: "Invalid", blurb: "Can't be applied safely — will be dropped on export.", icon: AlertTriangle, accent: "text-rose-600 dark:text-rose-400" },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Invalid export copy is inaccurate

The export path sanitizes structurally unsafe values and still emits their declarations, rather than dropping them. This message therefore gives users an incorrect account of the CSS that export will produce.

Suggested change
{ key: "invalid", label: "Invalid", blurb: "Can't be applied safely — will be dropped on export.", icon: AlertTriangle, accent: "text-rose-600 dark:text-rose-400" },
{ key: "invalid", label: "Invalid", blurb: "Contains CSS-breaking characters — will be sanitized on export.", icon: AlertTriangle, accent: "text-rose-600 dark:text-rose-400" },

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

…tion/Quality/Project)

Replace the flat, unlabelled icon rail with a grouped, always-labelled
navigation so users reason about areas of the design system instead of decoding
icons. The rail is now organised into four named groups:

- Foundations: Colors, Typography, Spacing, Shape, Motion
- Composition: Layout, Shadows, Effects, Macros, Components, Misc
- Quality:     Changes, Accessibility
- Project:     Presets, Install & export, Reference

Renames per the plan: Borders→Shape (it already holds radius), WCAG→
Accessibility, Themes→Presets, Install→"Install & export", Classes→Reference.
Labels update in App's panel-heading map and HomePanel too, so the sidebar, the
panel header and the Home overview all tell the same story (same order, names,
descriptions and counts).

Responsive: desktop shows the labelled column (w-52) with group headers and a
trailing count pill; mobile keeps the compact icon rail (w-14) with corner-dot
counts and tooltips — no change to the mobile fold behaviour.

Scope: this is the information-architecture + naming half of the redesign's
phase 4. Domain/panel ids, token classification and preview mapping are
unchanged, so it's non-disruptive. Content-level panel merges (a single Depth
panel combining Shadows+Effects, the Misc split, and moving focus-ring/touch
into an expanded Accessibility panel) are deliberately deferred to their own
focused follow-ups.

Updated tests-e2e/shell.spec.js NAV_LABELS to the new names. check, lint, all
257 unit tests and the shell e2e suite pass; desktop + mobile screenshot-verified.
… grouped undo

Two universal control fixes from the audit.

1) Grouped undo (src/lib/history.ts). A slider drag fires `input` on every
   tick, so a single gesture used to push dozens of near-identical snapshots and
   one Ctrl+Z rewound a single pixel. setOverrides now coalesces a run of
   consecutive edits to the *same* token within a short window into one history
   entry; a different edit or a pause starts a fresh group; undo/redo end the
   open group. Pure helpers (changedKeys, shouldCoalesce) are unit-tested.

2) Unified value editor (ValueField.svelte + src/lib/valueField.ts). An explicit
   three-mode control — Inherit / Value / Expression — replaces TokenRow's
   ad-hoc scale-picker/expand/button editor. An expression override (var()/
   calc()/clamp()/…) always resolves to Expression mode and is shown verbatim,
   never parsed down to a fallback number, so the next edit can't silently
   overwrite it; switching to a fixed Value is a deliberate tab click. Inherit
   shows the framework default and offers a "relink to a scale step" dropdown
   (folding in the old sibling-scale picker). detectMode/isExpression/
   splitValueUnit are unit-tested.

TokenRow keeps all its phase-1 context (role badge, inherits, used-by, Detached/
Invalid warnings) and now edits through ValueField — so All tokens and the
Changes panel both gain the unified editor. check + lint clean; 272 unit tests
(history 8, valueField 7) and the shell e2e suite pass; screenshot-verified that
a calc() override renders in Expression mode verbatim.

Scope note: this lands the unified generic editor and grouped undo. Migrating
the specialised SliderRow-based panels onto the same three-mode model, and the
shared scale control with detach diagnostics for radius/border/motion, are
tracked as follow-ups (the detach diagnostics already exist in the Changes panel
and tokenModel.scaleShadows).
@jackgranatowski
jackgranatowski force-pushed the ux/configurator-unified-controls branch from 4230a15 to 27cc308 Compare August 20, 2026 11:59
@jackgranatowski
jackgranatowski merged commit 35a1170 into main Aug 20, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants