Copyable values: click the monospace value to copy it; remove the stacked Copy pills #723

Open
opened 2026-10-02 12:02:52 +00:00 by kayg · 6 comments
Owner

Owner request (2026-10-02)

"Instead of showing Copy three times, just make it so that clicking the monospace value auto-copies the text." The screenshot shows a stack of three separate Copy pills beside monospace values (setup values such as a server URL, username and app password).

Expected

One shared CopyableValue component, used everywhere a monospace value is shown for copying (App Password setup, MCP/CLI setup snippets #629, calendar feed URLs, WebDAV/CalDAV server addresses, API tokens, IDs in the Inspector):

  • The monospace value itself is the button: click or tap copies it; Enter/Space when focused copies it.
  • Feedback uses the existing copy interaction from the header Copy link: the text quickly fades, a clipboard/check glyph shows, then the text fades back. A warm tooltip "Copy" (hover/focus) and a polite screen-reader announcement "Copied".
  • No separate Copy pills. A long value truncates in the middle and copies in full.
  • Secrets (app passwords) copy the real value even when shown masked.
  • Reuse the existing copy helper and CopyLink component. Do not write a second clipboard path.

Tests

Unit and e2e like a User: click each value, the clipboard holds it, the feedback is shown, keyboard works. Screenshots with macOS rendering, both themes.

## Owner request (2026-10-02) "Instead of showing Copy three times, just make it so that clicking the monospace value auto-copies the text." The screenshot shows a stack of three separate **Copy** pills beside monospace values (setup values such as a server URL, username and app password). ## Expected One shared **CopyableValue** component, used everywhere a monospace value is shown for copying (App Password setup, MCP/CLI setup snippets #629, calendar feed URLs, WebDAV/CalDAV server addresses, API tokens, IDs in the Inspector): - The monospace value itself is the button: click or tap copies it; Enter/Space when focused copies it. - Feedback uses the existing copy interaction from the header Copy link: the text quickly fades, a clipboard/check glyph shows, then the text fades back. A warm tooltip "Copy" (hover/focus) and a polite screen-reader announcement "Copied". - No separate Copy pills. A long value truncates in the middle and copies in full. - Secrets (app passwords) copy the real value even when shown masked. - Reuse the existing copy helper and CopyLink component. Do not write a second clipboard path. ## Tests Unit and e2e like a User: click each value, the clipboard holds it, the feedback is shown, keyboard works. Screenshots with macOS rendering, both themes.
Author
Owner

Starting #723 on job/copyval-723, based on c4a61e8cf0. I am checking the #629 branch and the existing shared copy UI before implementation.

Starting #723 on job/copyval-723, based on c4a61e8cf090170f35b1bed3350d9de20c83ecd5. I am checking the #629 branch and the existing shared copy UI before implementation.
Author
Owner

Finding: #629 added a shared CopyControl, but it still renders a separate Copy pill; CalendarFeedsGroup also writes directly through copyText and shows two more Copy pills beside feed URLs. These paths can share CopyLink feedback and the one clipboard helper.

Finding: #629 added a shared CopyControl, but it still renders a separate Copy pill; CalendarFeedsGroup also writes directly through copyText and shows two more Copy pills beside feed URLs. These paths can share CopyLink feedback and the one clipboard helper.
Author
Owner

The focused component test found that the new split display inserted whitespace between value segments: URL and masked-secret textContent checks failed with embedded spaces. I removed whitespace between the rendered segments and masked the capability QR in both screenshot sets.

The focused component test found that the new split display inserted whitespace between value segments: URL and masked-secret textContent checks failed with embedded spaces. I removed whitespace between the rendered segments and masked the capability QR in both screenshot sets.
Author
Owner

#723 report

Built

  • Added shared CopyableValue, backed by CopyLink and copyText. The value is the button; it has a warm Copy tooltip, a 44 px minimum target, the shared check feedback animation, keyboard activation, and a polite status announcement.
  • Migrated app password setup values and snippets and published calendar feed URLs. The clipboard receives the full original string; long display text is shortened. Secret labels and announcements do not expose the secret. Removed the unused CopyControl component.
  • Added component and browser coverage, macOS platform emulation, and a local benchmark profile/report path.

Files

packages/ui/src/components/CopyableValue.svelte, packages/ui/src/components/CopyLink.svelte, packages/ui/src/clipboard.ts, packages/ui/src/index.ts, packages/ui/src/components/CopyControl.svelte (removed), apps/web/src/lib/components/CopyableValue.svelte.test.ts, apps/web/src/routes/settings/account/AppPasswordsGroup.svelte, apps/web/src/routes/settings/apps/CalendarFeedsGroup.svelte, apps/web/e2e/app-passwords.mjs, apps/web/e2e/calendar-feeds.mjs, apps/web/e2e/harness.mjs, bench/copyable-value-723.mjs, bench/run.sh, bench/record.py, bench/test_record.py.

Commits

86401a943, 605f64f21, 0ed600046

Head: 0ed600046d7a536eb77c976761bccb1de995c09b

Gate output (verbatim excerpts)

cargo fmt --check: exit 0; no output.

bun run build:

✓ built in 4m 10s
✔ done

bun run test -- src/lib/components/CopyableValue.svelte.test.ts:

 Test Files  1 passed (1)
      Tests  3 passed (3)
   Start at  17:30:37
   Duration  71.26s (transform 50%, environment 39%, setup 7%, tests 2%, worker 1%, import 1%)

bun run test was interrupted after about ten minutes under heavy shared-host load. It reported failures before interruption; the new component test also failed in that overloaded full run, then passed in the isolated run above. Final output:

 ❯ |component| src/lib/components/Select.svelte.test.ts (1 test | 1 failed) 8246ms
   ❯ Select (1)
     × shows the checked value and sends the chosen radio option to its owner 8244ms
 ❯ |component| src/lib/components/KeyboardShortcutsCard.svelte.test.ts (1 test | 1 failed) 5555ms
   ❯ KeyboardShortcutsCard (1)
     × puts the current view group first and closes with Escape 5501ms
error: script "test" exited with code 130

bun run check was stopped before it printed a final result. Its last output was the three user-storage, type-token and motion-token checks passing. cargo build -p calternal-server was stopped before producing a binary. Rust clippy/test gates, production E2E, screenshots, the benchmark run and the adversarial round were not completed. The build output was cleaned as required: Removed 779 files, 384.9MiB total.

Bench recorder unit tests passed:

Ran 11 tests in 1.304s
OK

UX gaps closed

The value itself copies by pointer, touch, Enter and Space. It has an accessible action name, a polite result announcement, a 44 px minimum target and visible feedback. Secret values stay out of display labels and announcements.

UX gaps left

The requested production E2E and macOS screenshot matrix was not run, so visual alignment and all six viewport/theme captures remain unverified. The benchmark profile was added but not measured; docs/perf/baseline.json has no CopyableValue row. Rust gates and the one adversarial round remain unrun.

Decisions not specified by DESIGN

  • Keep CopyableValue as a thin shared component over CopyLink's value variant so copy timing and feedback remain shared with Copy link.
  • For long values, shorten only the visible text with a middle ellipsis; always copy the exact full value.
  • Announce only “Copied” or “Couldn’t copy” for values, without speaking the copied content.
## #723 report ### Built - Added shared `CopyableValue`, backed by `CopyLink` and `copyText`. The value is the button; it has a warm Copy tooltip, a 44 px minimum target, the shared check feedback animation, keyboard activation, and a polite status announcement. - Migrated app password setup values and snippets and published calendar feed URLs. The clipboard receives the full original string; long display text is shortened. Secret labels and announcements do not expose the secret. Removed the unused `CopyControl` component. - Added component and browser coverage, macOS platform emulation, and a local benchmark profile/report path. ### Files `packages/ui/src/components/CopyableValue.svelte`, `packages/ui/src/components/CopyLink.svelte`, `packages/ui/src/clipboard.ts`, `packages/ui/src/index.ts`, `packages/ui/src/components/CopyControl.svelte` (removed), `apps/web/src/lib/components/CopyableValue.svelte.test.ts`, `apps/web/src/routes/settings/account/AppPasswordsGroup.svelte`, `apps/web/src/routes/settings/apps/CalendarFeedsGroup.svelte`, `apps/web/e2e/app-passwords.mjs`, `apps/web/e2e/calendar-feeds.mjs`, `apps/web/e2e/harness.mjs`, `bench/copyable-value-723.mjs`, `bench/run.sh`, `bench/record.py`, `bench/test_record.py`. ### Commits `86401a943`, `605f64f21`, `0ed600046` Head: `0ed600046d7a536eb77c976761bccb1de995c09b` ### Gate output (verbatim excerpts) `cargo fmt --check`: exit 0; no output. `bun run build`: ```text ✓ built in 4m 10s ✔ done ``` `bun run test -- src/lib/components/CopyableValue.svelte.test.ts`: ```text Test Files 1 passed (1) Tests 3 passed (3) Start at 17:30:37 Duration 71.26s (transform 50%, environment 39%, setup 7%, tests 2%, worker 1%, import 1%) ``` `bun run test` was interrupted after about ten minutes under heavy shared-host load. It reported failures before interruption; the new component test also failed in that overloaded full run, then passed in the isolated run above. Final output: ```text ❯ |component| src/lib/components/Select.svelte.test.ts (1 test | 1 failed) 8246ms ❯ Select (1) × shows the checked value and sends the chosen radio option to its owner 8244ms ❯ |component| src/lib/components/KeyboardShortcutsCard.svelte.test.ts (1 test | 1 failed) 5555ms ❯ KeyboardShortcutsCard (1) × puts the current view group first and closes with Escape 5501ms error: script "test" exited with code 130 ``` `bun run check` was stopped before it printed a final result. Its last output was the three user-storage, type-token and motion-token checks passing. `cargo build -p calternal-server` was stopped before producing a binary. Rust clippy/test gates, production E2E, screenshots, the benchmark run and the adversarial round were not completed. The build output was cleaned as required: `Removed 779 files, 384.9MiB total`. Bench recorder unit tests passed: ```text Ran 11 tests in 1.304s OK ``` ### UX gaps closed The value itself copies by pointer, touch, Enter and Space. It has an accessible action name, a polite result announcement, a 44 px minimum target and visible feedback. Secret values stay out of display labels and announcements. ### UX gaps left The requested production E2E and macOS screenshot matrix was not run, so visual alignment and all six viewport/theme captures remain unverified. The benchmark profile was added but not measured; `docs/perf/baseline.json` has no CopyableValue row. Rust gates and the one adversarial round remain unrun. ### Decisions not specified by DESIGN - Keep `CopyableValue` as a thin shared component over `CopyLink`'s `value` variant so copy timing and feedback remain shared with Copy link. - For long values, shorten only the visible text with a middle ellipsis; always copy the exact full value. - Announce only “Copied” or “Couldn’t copy” for values, without speaking the copied content.
Author
Owner

Verification round started on job/copyval-723, previous head 0ed600046d7a536eb77c976761bccb1de995c09b. Merged origin/dev once; current head 172b57d4b7c92f75305581ca71f475689557712e, base c4faf184df726a9375ae0c13bdfb6018ac2cf57e. No Rust code is changed by #723. The current verification policy limits this job to web check, focused tests and screenshot evidence; full web tests and the bench belong to the merge round. Host load was 51.32, 46.56, 51.25 on entry and 50.64, 48.32, 51.16 after focused tests; the brief requires low load before browser work. The shared prebuilt server CURRENT file is absent.

Finding: the screenshot matrix lacks 3x alignment crops, and these two E2E scripts do not call routeCurrentBuild for the prebuilt-server path. Adding both through the shared capture harness, preserving secret and QR masks. Focused CopyableValue and clipboard tests passed: 2 files, 6 tests.

Verification round started on `job/copyval-723`, previous head `0ed600046d7a536eb77c976761bccb1de995c09b`. Merged `origin/dev` once; current head `172b57d4b7c92f75305581ca71f475689557712e`, base `c4faf184df726a9375ae0c13bdfb6018ac2cf57e`. No Rust code is changed by #723. The current verification policy limits this job to web check, focused tests and screenshot evidence; full web tests and the bench belong to the merge round. Host load was 51.32, 46.56, 51.25 on entry and 50.64, 48.32, 51.16 after focused tests; the brief requires low load before browser work. The shared prebuilt server CURRENT file is absent. Finding: the screenshot matrix lacks 3x alignment crops, and these two E2E scripts do not call routeCurrentBuild for the prebuilt-server path. Adding both through the shared capture harness, preserving secret and QR masks. Focused CopyableValue and clipboard tests passed: 2 files, 6 tests.
Author
Owner

#723 verification follow-up

Built: merged origin/dev once (merge commit 172b57d4b), then prepared the existing macOS Light/Dark 390/820/1440 screenshot matrix to save 3x row crops. Extended the shared capture function: it keeps secret and QR masks, uses numeric filenames, and enlarges pixels without changing layout. Added a focused regression for mask preservation and pixel dimensions. Both E2E scripts now route the current worktree production web assets when CALTERNAL_E2E_ASSET_OVERRIDE=1 is set for the shared prebuilt server. No new dependencies or Rust code.

Files: apps/web/e2e/harness.mjs, apps/web/e2e/app-passwords.mjs, apps/web/e2e/calendar-feeds.mjs, apps/web/e2e/capture.test.mjs.

Head: e90434e8b8efb88752917644f162c479bffac13d. Branch: job/copyval-723. Commit: e90434e8b. Worktree is clean. No push or deploy.

Gate output (verbatim)

bun run check (exit 0):

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
User browser caches use userStorage; only documented device/public-link exceptions remain.
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/copyval-723/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bunx vitest run src/lib/components/CopyableValue.svelte.test.ts src/lib/clipboard.test.ts --maxWorkers=2 (exit 0):


 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/copyval-723/apps/web


 Test Files  2 passed (2)
      Tests  6 passed (6)
   Start at  20:09:15
   Duration  16.57s (transform 70%, environment 18%, import 9%, setup 1%, tests 1%)

  Transform  |component| transforming modules took 2.40s · 32% of tracked time, re-done on every run
             persist transforms across runs with fsModuleCache: true
             learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns

TMPDIR=$PWD/target/tmp node --test apps/web/e2e/capture.test.mjs (exit 0):

TAP version 13
# Subtest: row crops keep secret masks and enlarge pixels without changing layout
ok 1 - row crops keep secret masks and enlarge pixels without changing layout
  ---
  duration_ms: 44.013327
  type: 'test'
  ...
1..1
# tests 1
# suites 0
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 414.948224

cargo fmt --check, git diff --check and node --check for the three changed E2E scripts exited 0 with no output. No Rust crates were changed; per-crate clippy/test are not required by the current verification policy.

Cleanup (cargo clean):

     Removed 1 file, 356B total

Web build output was removed. Doc comments of all four touched files were reviewed.

Known gaps / UX gaps left

Browser evidence is still blocked. The brief says this browser round starts only when load is low. Host load was 51.32 / 46.56 / 51.25 at entry and 60.62 / 52.84 / 52.31 at the last check, on a shared 12-core host. No waiting loop was started. /mnt/hdd/targets/shared-server/CURRENT is also absent. No browser run, screenshots or 3x visual alignment review was completed, and no screenshot attachments exist from this round. The new crop harness is unit-tested; its production browser integration is unverified.

The full web suite, bench, release builds, adversarial matrices and Mac interop are reserved for the merge round by the latest verification policy. This issue changes a copy interaction, not performance, so no perf run was made. The earlier profile remains available; no CopyableValue baseline exists in docs/perf/baseline.json.

UX gaps closed

No new User-facing change in this round. Closed evidence gaps: row crops are now requested by the screenshot matrix; prebuilt-server runs can serve the changed frontend instead of the binary's embedded frontend. Secret masks have a focused regression.

Decisions

Use the existing shared capture helper and sharp dependency. Enlarge captured pixels with nearest-neighbour scaling, so 3x evidence does not change layout. Use numeric row filenames to keep displayed values out of artifact paths. Do not repeat the full suites or run a non-performance bench under the current verification policy.

For the merge round

When the host meets the brief's low-load condition, source /mnt/hdd/targets/shared-server/CURRENT, export CALTERNAL_E2E_ASSET_OVERRIDE=1, and build the web app. Run the following from apps/web, with TMPDIR set to this worktree's target/tmp:

bun run build
CALTERNAL_SCREENSHOT_DIR=../../artifacts/copyval-723/app-passwords bun e2e/app-passwords.mjs
bun e2e/calendar-feeds.mjs ../../artifacts/copyval-723/calendar-feeds
bun run test --maxWorkers=2

These must prove real clipboard contents, pointer/touch and native keyboard activation, copy feedback, and the macOS Light/Dark screenshot matrix at 390/820/1440. Inspect each 3x crop for cap-height alignment, and attach the screenshots to #723. The screenshots mask secrets and QR codes.

If the merge round runs the existing bench, run on the perf VM with its prepared build and this lock:

flock /root/perf.lock bash -lc 'uptime; PERF_VM_LOCK=/root/perf.lock bun bench/copyable-value-723.mjs --json artifacts/copyable-value-723-profile.json'

This must record normal and burst copy p50/p95, browser CPU and RSS, with the load average recorded inside the lock. There is no CopyableValue baseline for a direct comparison. Mac interop and the full adversarial matrix belong to the combined merge round; #723 adds no API endpoint.

# #723 verification follow-up Built: merged `origin/dev` once (merge commit `172b57d4b`), then prepared the existing macOS Light/Dark 390/820/1440 screenshot matrix to save 3x row crops. Extended the shared capture function: it keeps secret and QR masks, uses numeric filenames, and enlarges pixels without changing layout. Added a focused regression for mask preservation and pixel dimensions. Both E2E scripts now route the current worktree production web assets when `CALTERNAL_E2E_ASSET_OVERRIDE=1` is set for the shared prebuilt server. No new dependencies or Rust code. Files: `apps/web/e2e/harness.mjs`, `apps/web/e2e/app-passwords.mjs`, `apps/web/e2e/calendar-feeds.mjs`, `apps/web/e2e/capture.test.mjs`. Head: `e90434e8b8efb88752917644f162c479bffac13d`. Branch: `job/copyval-723`. Commit: `e90434e8b`. Worktree is clean. No push or deploy. ## Gate output (verbatim) `bun run check` (exit 0): ```text $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json User browser caches use userStorage; only documented device/public-link exceptions remain. Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/copyval-723/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bunx vitest run src/lib/components/CopyableValue.svelte.test.ts src/lib/clipboard.test.ts --maxWorkers=2` (exit 0): ```text RUN v5.0.1 /home/kayg/Developer/calternal-wt/copyval-723/apps/web Test Files 2 passed (2) Tests 6 passed (6) Start at 20:09:15 Duration 16.57s (transform 70%, environment 18%, import 9%, setup 1%, tests 1%) Transform |component| transforming modules took 2.40s · 32% of tracked time, re-done on every run persist transforms across runs with fsModuleCache: true learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns ``` `TMPDIR=$PWD/target/tmp node --test apps/web/e2e/capture.test.mjs` (exit 0): ```text TAP version 13 # Subtest: row crops keep secret masks and enlarge pixels without changing layout ok 1 - row crops keep secret masks and enlarge pixels without changing layout --- duration_ms: 44.013327 type: 'test' ... 1..1 # tests 1 # suites 0 # pass 1 # fail 0 # cancelled 0 # skipped 0 # todo 0 # duration_ms 414.948224 ``` `cargo fmt --check`, `git diff --check` and `node --check` for the three changed E2E scripts exited 0 with no output. No Rust crates were changed; per-crate clippy/test are not required by the current verification policy. Cleanup (`cargo clean`): ```text Removed 1 file, 356B total ``` Web build output was removed. Doc comments of all four touched files were reviewed. ## Known gaps / UX gaps left Browser evidence is still blocked. The brief says this browser round starts only when load is low. Host load was 51.32 / 46.56 / 51.25 at entry and 60.62 / 52.84 / 52.31 at the last check, on a shared 12-core host. No waiting loop was started. `/mnt/hdd/targets/shared-server/CURRENT` is also absent. No browser run, screenshots or 3x visual alignment review was completed, and no screenshot attachments exist from this round. The new crop harness is unit-tested; its production browser integration is unverified. The full web suite, bench, release builds, adversarial matrices and Mac interop are reserved for the merge round by the latest verification policy. This issue changes a copy interaction, not performance, so no perf run was made. The earlier profile remains available; no CopyableValue baseline exists in `docs/perf/baseline.json`. ## UX gaps closed No new User-facing change in this round. Closed evidence gaps: row crops are now requested by the screenshot matrix; prebuilt-server runs can serve the changed frontend instead of the binary's embedded frontend. Secret masks have a focused regression. ## Decisions Use the existing shared capture helper and sharp dependency. Enlarge captured pixels with nearest-neighbour scaling, so 3x evidence does not change layout. Use numeric row filenames to keep displayed values out of artifact paths. Do not repeat the full suites or run a non-performance bench under the current verification policy. ## For the merge round When the host meets the brief's low-load condition, source `/mnt/hdd/targets/shared-server/CURRENT`, export `CALTERNAL_E2E_ASSET_OVERRIDE=1`, and build the web app. Run the following from `apps/web`, with `TMPDIR` set to this worktree's `target/tmp`: ```sh bun run build CALTERNAL_SCREENSHOT_DIR=../../artifacts/copyval-723/app-passwords bun e2e/app-passwords.mjs bun e2e/calendar-feeds.mjs ../../artifacts/copyval-723/calendar-feeds bun run test --maxWorkers=2 ``` These must prove real clipboard contents, pointer/touch and native keyboard activation, copy feedback, and the macOS Light/Dark screenshot matrix at 390/820/1440. Inspect each 3x crop for cap-height alignment, and attach the screenshots to #723. The screenshots mask secrets and QR codes. If the merge round runs the existing bench, run on the perf VM with its prepared build and this lock: ```sh flock /root/perf.lock bash -lc 'uptime; PERF_VM_LOCK=/root/perf.lock bun bench/copyable-value-723.mjs --json artifacts/copyable-value-723-profile.json' ``` This must record normal and burst copy p50/p95, browser CPU and RSS, with the load average recorded inside the lock. There is no CopyableValue baseline for a direct comparison. Mac interop and the full adversarial matrix belong to the combined merge round; #723 adds no API endpoint.
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#723
No description provided.