feat(app): widget settings view — rail, scheme tiles, scope menu (#572 part 3) - #578
feat(app): widget settings view — rail, scheme tiles, scope menu (#572 part 3)#578omridevk wants to merge 6 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
8a48ee9 to
f322635
Compare
f45e68d to
a4acc0b
Compare
…tion Adds a SegmentGroup primitive built on Ark's segment group. Its selected indicator is a real layout animation: zag publishes the selected item's rect as --left/--top/--width/--height and reads --transition-duration and --transition-timing-function back out, so the indicator slides between segments the same way the tabs indicator does. Under prefers-reduced-motion the duration collapses and the indicator snaps. The switch thumb moved with `translate`, which trans-btn never listed among its transitioned properties, so the thumb jumped. It now transitions `translate` and snaps under reduced motion. Refs #572 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…part 3) A nested settings route under the panel: a layout route with a trace-rail section nav plus an outlet, an appearance child, and an index that redirects to it. The session rail menu opens it; the settings header carries the two-line SETTINGS microlabel over the active section title with back and close. The appearance section renders the scheme setting as three SegmentGroup preview tiles that write through settings.set the moment they change, with no Save button. The provenance badge is the scope control: it opens an Ark menu with apply to all projects, use global value (only when a project override shadows a global one), and reset to default, and flashes a saved check as it flips to its new source. The resolved scheme now folds the stored preference over the host detection ladder, so auto keeps following the page live while light and dark pin the widget. A settings-changed notification on the session stream refetches the resolved settings, so a change made in another tab or by the agent repaints an open widget without a reload. The section rail reuses the trace-rail geometry with a complement clip: separate gray layers above and below the live band mean no pixel is ever painted by both colors, arms start at the spine stroke's outer edge, and centerlines snap to half-integers. A container query on the panel flips a custom property that switches the rail between the vertical spine and a horizontal underline, and every property that query re-decides lives in styles.css rather than as a utility class, since the generated utility layer is emitted last. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ovenance Review round on the settings view. Writes now follow the resolved source: editing a value that comes from the global layer writes global and the badge stays GLOBAL, while project and default sources write to the project. The badge menu gains "Set for this project only" so a global value can be forked into a project override, and the footer names the layer the next write lands in. The view is rebuilt against the approved mockup: the provenance badge is a small mono chip tinted per source rather than an outlined button, its menu uses the panel's own menu recipe, the section rail carries all three rows with their icons (Composer and Connection disabled until their settings land) so the rail has its full geometry, and the nav label reads SETTINGS. The tiles are a plain three-column grid with no segmented-control wrapper and no hover fill, selection reads as the accent ring alone, and the card sits at the top of a sunken pane with the scope line pinned to the bottom. The container query never fired when the panel was dragged narrow: its breakpoint sat at 26rem, below the panel's own 448px drag minimum, so no reachable width could match. It now sits at 30rem, between that minimum and the default width, and the rail's clip edges and horizontal underline are percentage-based so a fast resize cannot leave a stale frame behind. The saved-check flash is gone; a write is acknowledged by the value and the badge changing, not by an indicator that appears and vanishes. Loading now renders the widget's skeleton vocabulary, a failed read or write renders an inline error with a retry, and the optimistic selection rolls back when the write fails. SegmentGroup grows a `plain` variant so a consumer can render fully custom items without the segmented-control chrome, which is what these tiles need; importing Ark directly is a lint error outside ui-kit-system. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The chip kept its scale and mono typography but nothing said it could be clicked. It now carries a chevron, a resting border tinted per source, and the panel's own small-pill interaction recipe lifted from the session selector trigger: the fill strengthens, the border lifts and the text lifts over 120ms, and an open menu holds that same look while the chevron flips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reads now come from the single settings.get call, which carries the effective value, its controlling layer, each layer's raw value and each layer's revision. The scope badge derives "use global value" and the reset layers from that provenance instead of mining the history sidecar, so the panel makes one read where it used to make two. Ordinary edits always write the project layer. A value inherited from the global layer therefore moves to this project when you change it, the badge flips to PROJECT and the footer says so, and the global value stays put for other projects. Applying a value to all projects goes through the single settings.applyGlobally server op rather than a client-sequenced set plus clear. Every write sends the layer revision it expects. A revision conflict reloads the settings and says what happened instead of retrying over a value someone else wrote; an unparseable settings file says so and offers no retry until the file is fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
f322635 to
9e4f1e2
Compare
Settings routes move under /panel/$sessionId/settings so the chat session stays mounted while settings is open: back is an instant child swap with the thread rendered from the live store, live settings repaint keeps working inside the view, and the back control no longer depends on history state. The session chrome hides while a settings child is active and the running blocker exempts settings navigation. - View motion: settings slides in (anim-tab-right) and plays the presence-out exit before navigating back, skipped under reduced motion. - Horizontal rail: the active-tab underline drew one pixel outside the layer svg box (border-box height vs content-box layer) and never showed; plan now measures clientHeight. Narrow tab row gets real tab affordances: taller rows, top radii, spacing. - Disabled sections read disabled: mono SOON chip, dimmed icon, not-allowed cursor, and the nav is no longer text-selectable. - The nav rail scales with the panel (clamp over cqi) instead of staying 156px on a wide panel. - Scheme writes serialize through a queue and read fresh layer revisions per write, so an edit right after applyGlobally no longer races its own revision bump into a 409 rollback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
New commit: settings nested under the session route (chat stays mounted, back is instant with no refetch flash), enter/exit view motion, horizontal rail underline fixed and tab row redesigned, SOON chips on disabled sections, nav rail scales with panel width, and scheme writes serialize with fresh revisions (fixes the applyGlobally-then-edit 409 race). All verified live in the browser; gates green: 201 app + 22 embed unit + 149 e2e, lint, format, fallow 0 introduced. |
Stacked on #592 (
issue-572-settings-foundation-v2). This branch is rebased onto that PR's head and contains only the widget settings UI; review #592 first, and merge it first.Part 3 of #572: the widget settings view. A nested settings route under the panel with a trace-rail section nav, an appearance section whose scheme preview tiles apply instantly, and a provenance badge that doubles as the scope menu.
SegmentGrouplands in@conciv/ui-kit-systemas an Ark segment group whose indicator slides between segments, plus a switch thumb that actually travels; both snap under reduced motion.Adaptation to the #592 contract
The branch was originally written against the closed #574 (sqlite) foundation. The rebase dropped that foundation commit wholesale and replayed the UI work onto #592, so nothing server-side or contract-side comes from this branch any more. What changed in the UI to meet the shipped contract:
One read.
settings.getreturns the effective value, the controlling layer, each layer's raw value and validity, and each layer's revision. The scope badge used to open a secondhistoryquery to decide whether a global value existed; it now readslayers.global.statefrom the same read. There is noinspectcall and no reference to the oldauditname. One read replaces two.Key namespaced.
schemeisappearance.scheme, and the fallback is read from the registry entry rather than hardcoded.New shapes.
SettingsRead/SettingsKeyViewreplaceResolvedSetting[], so the optimistic cache update rewrites the key view rather than a flat row array. The optimistic value and source are computed by the sameproject ?? global ?? defaultrule the server uses, applied to the layers the write is about to produce, instead of three hand-written expected states.Ordinary edits always write the project layer. Previously the write followed provenance, so editing a globally-scoped value silently rewrote the global layer for every project. Now a value inherited from the global layer moves to this project when you change it: the badge flips
GLOBALtoPROJECT, the footer reads "Changes save automatically to this project.", the announcement says the global setting still applies elsewhere, and the badge menu offers "Use global value" to go back. The global value is left intact.Apply-to-all is one server call. The branch composed a global
setplus a projectclearclient-side. It now callssettings.applyGlobally, which does both under one lock, oneopIdand one settings-changed event. Covered by a test that counts the wire calls.Every write carries its expected revision, read from
get'slayers[scope].revision. AREVISION_CONFLICTreloads the settings and surfaces a non-blocking inline message rather than retrying over whatever the other writer stored.LAYER_UNPARSEABLEsays the file has a syntax error and offers no retry until it is fixed;LOCK_TIMEOUTand the value/key errors get their own copy. Live-region announcements are worded separately from the inline copy so both surfaces are independently assertable.Event payload. The settings-changed listener keys on the event name only and invalidates the settings query, which is idempotent, so the
{opId, keys, scopes}payload change needed no listener change.Test plumbing.
bootCoreKit/bootEmbedKitforwardglobalSettingsDir, matchingcreateApp's actual option name (the branch passedglobalStateDir, whichcreateAppdoes not accept).No visual change: layout, rail geometry, motion, spacing and tokens are exactly as approved.
Tests
13 integration tests in
packages/embed/tests/e2e/settings-panel.it.test.ts, against the prebuilt embed and a real server writing real files in temp dirs. Three are new for the contract deltas above:The conflict test does not stub anything: it rewrites the outgoing request's
expectedRevisionat the network boundary so the real server returns a real 409.TDD, batched. The three tests were written first and run once against the un-adapted branch: 3 failed for their own reasons (badge read
GLOBALwherePROJECTwas expected; zeroapplyGloballycalls; no conflict message), 10 passed. After the fix, 13/13. Revert-check: with only the behavioral source reverted and the tests untouched, the same 3 failed again and the other 10 stayed green.Gates
TURBO_CONCURRENCY=70% pnpm turbo run typecheck --filter=@conciv/app --filter=@conciv/embed --filter=@conciv/extension-testkit --filter=@conciv/ui-kit-system --filter=@conciv/client- pass (36/36)TURBO_CONCURRENCY=1 VITEST_MAX_FORKS=1 pnpm turbo run test --concurrency=1 --filter=@conciv/app --filter=@conciv/ui-kit-system --filter=@conciv/client --filter=@conciv/extension-testkit- pass (324 tests)TURBO_CONCURRENCY=1 VITEST_MAX_FORKS=1 pnpm turbo run test --concurrency=1 --filter=@conciv/embed- pass (22 unit, 146 integration)pnpm lint- pass (0 errors)pnpm format:check- passpnpm exec fallow audit --changed-since origin/issue-572-settings-foundation-v2 --format json- verdictpass, 0 introducedpnpm exec conciv-publish check-changesets --require-coverage --base origin/issue-572-settings-foundation-v2- passFlagged
Resetting to the default when both layers hold a value still sends twoResolved (delta 15). #592 landed aclearcalls, one per layer.settings.reset({key, expectedRevisions})op mirroringapplyGlobally. Reset now goes through that single call instead of a per-scopeclearloop, covered by a test that counts the wire calls (onesettings.reset, zeroclear).The panel does not surface
layers[scope].path,format,parseErroror the both-files-presentwarning. Nothing in the frozen design has a place for a file path, and adding one is a visual change. The unparseable-file case does reach the user through the write error copy.Part 3 of #572.
🤖 Generated with Claude Code