COMPOSER: one control scale (NLP chips were missed by the HIG audit) + mode selector collapses to one item and expands inline over the chips with horizontal progressive blur #327

Closed
opened 2026-09-28 09:59:57 +00:00 by kayg · 10 comments
Owner

Owner (2026-09-28, iPhone screenshot of the composer: 'Today' chip, '3:20 PM – 4:20 PM' NLP chips, the 4-icon mode pill top right, the tools pill, the send button): 'look at the vastly different sizes here? the nlp chips weren't considered in the apple hig audit? there's not enough space for the top right 4 selectors btw. compress to a single item, expand inline horizontally on tap. when expanded, the nlp chips should go under the expanded pill with progressive blur. basically horizontal progressive blur for anything that comes close to the expanded 4 selector pill and goes under it.'

Orchestrator defaults (the owner may correct them):

  1. One control scale in the composer (and reuse the tokens app-wide): the NLP chips and the Today chip share ONE chip size (a visual height of ≈28–32 pt; the hit area stays ≥44 pt via the shared touch-hit expansion, per the #278 HIG audit, which missed the NLP chips); the tools pill, the mode selector and the send button share ONE pill size. Tokens only (#287/#299), no literals. Check chip text size and weight against the type scale.
  2. Mode selector collapses: it shows only the current mode's icon (log/task/note/event) as a single-pill button, top right. Tap → it expands inline, leftwards, into the 4 modes with the tab-bar width-morph spring (#236/#291). Picking a mode, tapping elsewhere or Esc collapses it again. ⌘1–4 (or the existing shortcuts) still switch directly. Accessibility: a menu-button/radiogroup with the current mode announced; reduced motion = instant.
  3. The chips go under it: the NLP chips row shares the top row with the collapsed selector. While the selector is expanded, the chips stay in place and slide under the pill; at the pill's leading edge there is a horizontal progressive blur (the same progressive-blur primitive as the headers, rotated to horizontal via a mask gradient), so whatever gets close to the pill blurs as it passes under. Chips remain scrollable horizontally when they overflow.
  4. Desktop too: one composer on every screen size (the owner's consistency rule).
    Evidence: 390 / 820 / 1440 px, light and dark, with: collapsed; expanded over long chips; a frame strip of the expand/collapse; reduced motion. Zoom-check chip and pill vertical alignment on one baseline.
Owner (2026-09-28, iPhone screenshot of the composer: 'Today' chip, '3:20 PM – 4:20 PM' NLP chips, the 4-icon mode pill top right, the tools pill, the send button): 'look at the vastly different sizes here? the nlp chips weren't considered in the apple hig audit? there's not enough space for the top right 4 selectors btw. compress to a single item, expand inline horizontally on tap. when expanded, the nlp chips should go under the expanded pill with progressive blur. basically horizontal progressive blur for anything that comes close to the expanded 4 selector pill and goes under it.' Orchestrator defaults (the owner may correct them): 1. **One control scale in the composer** (and reuse the tokens app-wide): the NLP chips and the Today chip share ONE chip size (a visual height of ≈28–32 pt; the hit area stays ≥44 pt via the shared touch-hit expansion, per the #278 HIG audit, which missed the NLP chips); the tools pill, the mode selector and the send button share ONE pill size. Tokens only (#287/#299), no literals. Check chip text size and weight against the type scale. 2. **Mode selector collapses:** it shows only the current mode's icon (log/task/note/event) as a single-pill button, top right. Tap → it expands **inline, leftwards**, into the 4 modes with the tab-bar width-morph spring (#236/#291). Picking a mode, tapping elsewhere or Esc collapses it again. ⌘1–4 (or the existing shortcuts) still switch directly. Accessibility: a menu-button/radiogroup with the current mode announced; reduced motion = instant. 3. **The chips go under it:** the NLP chips row shares the top row with the collapsed selector. While the selector is expanded, the chips stay in place and slide under the pill; at the pill's leading edge there is a **horizontal progressive blur** (the same progressive-blur primitive as the headers, rotated to horizontal via a mask gradient), so whatever gets close to the pill blurs as it passes under. Chips remain scrollable horizontally when they overflow. 4. **Desktop too:** one composer on every screen size (the owner's consistency rule). Evidence: 390 / 820 / 1440 px, light and dark, with: collapsed; expanded over long chips; a frame strip of the expand/collapse; reduced motion. Zoom-check chip and pill vertical alignment on one baseline.
Author
Owner

Owner correction (2026-09-28): desktop keeps the full 4-mode selector ('there's enough space on desktop, this mode selector compression animation is not needed there'). The collapse/expand-inline behaviour and the horizontal progressive blur apply only on phones (the SurfaceViewport rule: <768 px or coarse pointer). The one control scale (point 1) still applies on every screen size.

Owner correction (2026-09-28): **desktop keeps the full 4-mode selector** ('there's enough space on desktop, this mode selector compression animation is not needed there'). The collapse/expand-inline behaviour and the horizontal progressive blur apply **only on phones** (the SurfaceViewport rule: <768 px or coarse pointer). The one control scale (point 1) still applies on every screen size.
Author
Owner

Starting work on job/composer-scale from dev at base SHA 413ccaa7b7f7f509ec74176a20569db049ae6171. I am inspecting the existing Composer and shared UI tokens before changing only the chip scale and phone mode selector.

Starting work on `job/composer-scale` from `dev` at base SHA `413ccaa7b7f7f509ec74176a20569db049ae6171`. I am inspecting the existing Composer and shared UI tokens before changing only the chip scale and phone mode selector.
Author
Owner

Finding from the existing Composer source: NLP chips had a 24 px minimum height and --text-caption; the mode selector used the full control height inside the capsule frame, while the tools used the compact control height and the fine-pointer send button added 4 px. I have moved chip dimensions to shared --ui-scale tokens and aligned the mode selector, tools and send button to --control-h.

Finding from the existing Composer source: NLP chips had a 24 px minimum height and `--text-caption`; the mode selector used the full control height inside the capsule frame, while the tools used the compact control height and the fine-pointer send button added 4 px. I have moved chip dimensions to shared `--ui-scale` tokens and aligned the mode selector, tools and send button to `--control-h`.
Author
Owner

Finding from production E2E at 1440 px in Paper: the Composer mode radiogroup measured 36 px high while the tools pill and Send control measured 30 px. The mode selector inherited --island-hit: --control-h, which includes the larger mode-tray segment height. I am setting the Composer selector's shared --island-hit role to --control-h-compact inside the existing pill frame; this preserves the touch hit outset and matches the action pill geometry.

Finding from production E2E at 1440 px in Paper: the Composer mode radiogroup measured 36 px high while the tools pill and Send control measured 30 px. The mode selector inherited `--island-hit: --control-h`, which includes the larger mode-tray segment height. I am setting the Composer selector's shared `--island-hit` role to `--control-h-compact` inside the existing pill frame; this preserves the touch hit outset and matches the action pill geometry.
Author
Owner

Finding from the 820 px coarse-pointer E2E run: after the phone selector opened, its track could still measure 44 px at the fixed 250 ms sample, while a second production probe measured 184 px once the same transition settled. The layout rule expands correctly; the screenshot assertion sampled before it was ready under shared-host load. I am replacing the fixed pause with a condition wait for the track to exceed twice its collapsed width before inspecting or selecting it.

Finding from the 820 px coarse-pointer E2E run: after the phone selector opened, its track could still measure 44 px at the fixed 250 ms sample, while a second production probe measured 184 px once the same transition settled. The layout rule expands correctly; the screenshot assertion sampled before it was ready under shared-host load. I am replacing the fixed pause with a condition wait for the track to exceed twice its collapsed width before inspecting or selecting it.
Author
Owner

Finding from the merged production E2E at the 820 px screenshot pass: all save, attachment, voice, and draft flows passed, then the second-open check after Escape threw ReferenceError: triggerBox is not defined at composer.mjs:193. The first trigger measurement was block-scoped to the initial open check. I am re-reading the button bounds after the second click; this is an E2E helper issue, not a selector rendering failure.

Finding from the merged production E2E at the 820 px screenshot pass: all save, attachment, voice, and draft flows passed, then the second-open check after Escape threw `ReferenceError: triggerBox is not defined` at composer.mjs:193. The first trigger measurement was block-scoped to the initial open check. I am re-reading the button bounds after the second click; this is an E2E helper issue, not a selector rendering failure.
Author
Owner

Finding from the 820 px production probe: the visual fixture inherited Task mode and included p1 / every week. The server returned task tags but no tag run spans for that parse, so the Composer did not render tag chips when the test checked overflow. In Log mode, the same API returns tag spans. I am making the chip-overflow fixture Log-specific and waiting for its three real tag chips; only the 390 px phone capture will require overflow, since the 820 px row can fit.

Finding from the 820 px production probe: the visual fixture inherited Task mode and included `p1` / `every week`. The server returned task tags but no `tag` run spans for that parse, so the Composer did not render tag chips when the test checked overflow. In Log mode, the same API returns tag spans. I am making the chip-overflow fixture Log-specific and waiting for its three real tag chips; only the 390 px phone capture will require overflow, since the 820 px row can fit.
Author
Owner

Production screenshot run found that hydrates from the signed-in User after the local palette is set. The 1440 dark capture requested Tokyo Night but measured and . I am updating the capture fixture to save both the server-backed scheme and local palette family before navigation, then reassert the rendered theme before each screenshot.

Production screenshot run found that hydrates from the signed-in User after the local palette is set. The 1440 dark capture requested Tokyo Night but measured and . I am updating the capture fixture to save both the server-backed scheme and local palette family before navigation, then reassert the rendered theme before each screenshot.
Author
Owner

Correction: the production screenshot assertion measured dark=false and data-theme=tokyo-night-day when the 1440 Tokyo Night dark screenshot was requested. The signed-in User preference loads from the server after the local palette. The capture fixture now saves both that server-backed scheme and the local palette family before navigation, and asserts the rendered theme before every screenshot.

Correction: the production screenshot assertion measured dark=false and data-theme=tokyo-night-day when the 1440 Tokyo Night dark screenshot was requested. The signed-in User preference loads from the server after the local palette. The capture fixture now saves both that server-backed scheme and the local palette family before navigation, and asserts the rendered theme before every screenshot.
Author
Owner

Completed composer-scale on branch job/composer-scale.

Head: 0e4db3de3eb9401ed447d6331b48f7953a421ba4 (pushed). The branch includes the one required merge from dev; it was not merged into dev.

Built

  • Scaled Composer chips with role tokens and touch-sized controls.
  • Added the compact phone/coarse-pointer mode selector with the existing #236/#291 spring, while fine-pointer desktop keeps all four modes visible.
  • Added progressive edge fades for the horizontally scrolling chip row.
  • Added production screenshot coverage for the light/dark, device-width, expanded, and reduced-motion states, plus a mode-selector frame strip.

Files: packages/ui/src/tokens.css, packages/ui/src/components/composer/ModePill.svelte, packages/ui/src/components/ProgressiveBlur.svelte, apps/web/src/lib/composer/Composer.svelte, apps/web/e2e/composer.mjs.

Production evidence

The production E2E flow passed: composer e2e: all flows passed; CSP REPORTS composer: 0 across 11 pages. Captures assert the actual theme before each screenshot. All requested artifacts are attached to this issue:

Gates

cargo fmt --check: exit 0; no stdout or stderr.

cargo clippy --all-targets -- -D warnings: exit 0; output:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 19m 54s

cargo test: exit 101. The failing output:

---- tests::retitle_replays_after_each_persisted_step stdout ----

thread 'tests::retitle_replays_after_each_persisted_step' (3568041) panicked at crates/plugins/notes/src/lib.rs:6457:39:
called `Result::unwrap()` on an `Err` value: ApiError(500, ErrorEnvelope { error: ApiError { code: Internal, message: "Notes operation failed", details: None } })
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    tests::retitle_replays_after_each_persisted_step

test result: FAILED. 108 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 51.20s

error: test failed, to rerun pass `-p calternal-plugin-notes --lib`

bun run check: exit 0; output:

$ svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/composer-scale/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test: exit 0; output:

$ vitest run

 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/composer-scale/apps/web
...
 Test Files  118 passed (118)
      Tests  767 passed (767)
   Start at  04:30:54
   Duration  117.64s (transform 43%, environment 30%, import 15%, tests 9%, setup 3%)

Environment  |component| jsdom was created 33 times · 201.36s total, 46% of tracked time
             create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files)
             learn more: https://vitest.dev/guide/improving-performance#test-environments

Known gap

The workspace test failure is in the Notes plugin and outside this Composer change. Code tracing points to its test fixture: top-level Notes recovery runs the daily-note navigation migration, which queries users, while that test setup applies only Notes and Tags migrations. The dedicated migration test creates the users table. I left the unrelated fixture and expectation unchanged.

Decisions

Followed the issue correction: compact and animate the selector below the phone breakpoint or for coarse pointers; keep all four choices visible on fine-pointer desktop. Reused the existing capsule spring and used horizontal chip scrolling with progressive edge fades.

Completed `composer-scale` on branch `job/composer-scale`. Head: `0e4db3de3eb9401ed447d6331b48f7953a421ba4` (pushed). The branch includes the one required merge from `dev`; it was not merged into `dev`. ## Built - Scaled Composer chips with role tokens and touch-sized controls. - Added the compact phone/coarse-pointer mode selector with the existing #236/#291 spring, while fine-pointer desktop keeps all four modes visible. - Added progressive edge fades for the horizontally scrolling chip row. - Added production screenshot coverage for the light/dark, device-width, expanded, and reduced-motion states, plus a mode-selector frame strip. Files: `packages/ui/src/tokens.css`, `packages/ui/src/components/composer/ModePill.svelte`, `packages/ui/src/components/ProgressiveBlur.svelte`, `apps/web/src/lib/composer/Composer.svelte`, `apps/web/e2e/composer.mjs`. ## Production evidence The production E2E flow passed: `composer e2e: all flows passed`; `CSP REPORTS composer: 0 across 11 pages`. Captures assert the actual theme before each screenshot. All requested artifacts are attached to this issue: - Main 390/820/1440 light and dark matrix: [390 Paper](https://git.kayg.org/attachments/037122b7-73fd-4b35-a636-c789968c0ec0), [820 Paper](https://git.kayg.org/attachments/ebb788ec-873b-452f-9dc5-cacc39b5b0cb), [1440 Paper](https://git.kayg.org/attachments/704ce7dd-eebf-4222-9424-6e0d6af90588), [390 Tokyo Night](https://git.kayg.org/attachments/930bb43a-420e-44da-bcbc-0019f70dad93), [820 Tokyo Night](https://git.kayg.org/attachments/5bc4acf7-de2d-4fbf-bf3a-8307f6a09761), [1440 Tokyo Night](https://git.kayg.org/attachments/ee65d371-acde-4c44-8972-bf7350e4c6cb). - Expanded selector over long chips: [390 Paper](https://git.kayg.org/attachments/43f57329-c49a-4772-9e2d-1e8cd450dca0), [820 Paper](https://git.kayg.org/attachments/42c73a7c-6612-4bf5-98e4-69b2d892de7b), [390 Tokyo Night](https://git.kayg.org/attachments/b3bc3f0b-e954-4313-abbf-427412725312), [820 Tokyo Night](https://git.kayg.org/attachments/42b7c22e-d72d-41e5-b4c0-af2dee2329b3). - Reduced motion: [390 Paper](https://git.kayg.org/attachments/1be4024f-d04a-4f5b-9d40-19561d37e170), [820 Paper](https://git.kayg.org/attachments/ae565c6d-892c-434e-b75f-939ef2c405f0), [390 Tokyo Night](https://git.kayg.org/attachments/33cf5c99-bae4-4f08-917a-cee2d7e252c8), [820 Tokyo Night](https://git.kayg.org/attachments/43a7cedf-b7c7-4c9a-b6c8-5cab82b51dcc). - [390 Paper mode-selector frame strip](https://git.kayg.org/attachments/f54946ac-1025-4f8b-a884-eaa48638aff1). ## Gates `cargo fmt --check`: exit 0; no stdout or stderr. `cargo clippy --all-targets -- -D warnings`: exit 0; output: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 19m 54s ``` `cargo test`: exit 101. The failing output: ``` ---- tests::retitle_replays_after_each_persisted_step stdout ---- thread 'tests::retitle_replays_after_each_persisted_step' (3568041) panicked at crates/plugins/notes/src/lib.rs:6457:39: called `Result::unwrap()` on an `Err` value: ApiError(500, ErrorEnvelope { error: ApiError { code: Internal, message: "Notes operation failed", details: None } }) note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::retitle_replays_after_each_persisted_step test result: FAILED. 108 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 51.20s error: test failed, to rerun pass `-p calternal-plugin-notes --lib` ``` `bun run check`: exit 0; output: ``` $ svelte-kit sync && svelte-check --tsconfig ./tsconfig.json Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/composer-scale/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test`: exit 0; output: ``` $ vitest run RUN v5.0.1 /home/kayg/Developer/calternal-wt/composer-scale/apps/web ... Test Files 118 passed (118) Tests 767 passed (767) Start at 04:30:54 Duration 117.64s (transform 43%, environment 30%, import 15%, tests 9%, setup 3%) Environment |component| jsdom was created 33 times · 201.36s total, 46% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) learn more: https://vitest.dev/guide/improving-performance#test-environments ``` ## Known gap The workspace test failure is in the Notes plugin and outside this Composer change. Code tracing points to its test fixture: top-level Notes recovery runs the daily-note navigation migration, which queries `users`, while that test setup applies only Notes and Tags migrations. The dedicated migration test creates the `users` table. I left the unrelated fixture and expectation unchanged. ## Decisions Followed the issue correction: compact and animate the selector below the phone breakpoint or for coarse pointers; keep all four choices visible on fine-pointer desktop. Reused the existing capsule spring and used horizontal chip scrolling with progressive edge fades.
kayg closed this issue 2026-09-29 03:37:50 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#327
No description provided.