UI: consolidate three copy-link components into one (reuse gate) #73

Closed
opened 2026-09-24 19:12:19 +00:00 by kayg · 8 comments
Owner

Reuse-gate defect (CLAUDE.md: "Duplicated logic is a defect"). Three UI branches built copy-link controls in parallel, and all three are now on main in packages/ui:

  • components/CopyLinkAction.svelte: a standalone icon Pill, used by Settings.
  • components/CopyLinkButton.svelte plus clipboard.ts (copyText, absoluteLink): used by Files.
  • components/CopyLinkSegment.svelte: a .pg-btn segment inside a PillGroup, used by Calendar and the Notes header.

Consolidate into one CopyLink component with a variant (pill | segment | menu-item) and one clipboard helper (clipboard.ts). Keep calternal.js's DateDivider copy→check interaction (a single duration: pick the calternal.js value) and one announcement strategy: a polite live region, with an optional onannounce for a shared region. Migrate every consumer, delete the other two, and make sure every deep-linkable surface (DESIGN §33) still has Copy link. Screenshots before and after, in Paper White and Tokyo Night.

Reuse-gate defect (CLAUDE.md: "Duplicated logic is a defect"). Three UI branches built copy-link controls in parallel, and all three are now on main in `packages/ui`: - `components/CopyLinkAction.svelte`: a standalone icon Pill, used by Settings. - `components/CopyLinkButton.svelte` plus `clipboard.ts` (`copyText`, `absoluteLink`): used by Files. - `components/CopyLinkSegment.svelte`: a `.pg-btn` segment inside a PillGroup, used by Calendar and the Notes header. Consolidate into **one** `CopyLink` component with a `variant` (`pill` | `segment` | `menu-item`) and one clipboard helper (`clipboard.ts`). Keep calternal.js's DateDivider copy→check interaction (a single duration: pick the calternal.js value) and one announcement strategy: a polite live region, with an optional `onannounce` for a shared region. Migrate every consumer, delete the other two, and make sure every deep-linkable surface (DESIGN §33) still has Copy link. Screenshots before and after, in Paper White and Tokyo Night.
Author
Owner

Starting work on this in a UI polish branch (ui-polish-73, not pushed). Plan: one CopyLink component in packages/ui with variant: 'pill' | 'segment' | 'menu-item', one clipboard helper (clipboard.ts), calternal.js DateDivider copy to check glyph swap with its 500 ms duration, one polite live region with optional onannounce. All consumers in apps/web and packages/ui migrate; the three old components are deleted.

Starting work on this in a UI polish branch (`ui-polish-73`, not pushed). Plan: one `CopyLink` component in `packages/ui` with `variant: 'pill' | 'segment' | 'menu-item'`, one clipboard helper (`clipboard.ts`), calternal.js DateDivider copy to check glyph swap with its 500 ms duration, one polite live region with optional `onannounce`. All consumers in `apps/web` and `packages/ui` migrate; the three old components are deleted.
Author
Owner

Done on branch ui-polish-73 (commit 5f7ef7c, plus be6ca07 for a name clash). Not pushed.

  • One CopyLink in packages/ui/src/components/CopyLink.svelte with variant: 'pill' | 'segment' | 'menu-item'. CopyLinkAction, CopyLinkButton and CopyLinkSegment are deleted. index.ts exports only CopyLink (plus COPY_FEEDBACK_MS and CopyLinkVariant).
  • The glyph swap is calternal.js DateDivider's copy to check, with its 500 ms duration (COPY_FEEDBACK_MS).
  • There is one announcement strategy: the control's own polite live region, or the caller's shared region through onannounce. Quick Look no longer shows its own toast on top of that.
  • There is one clipboard helper, clipboard.ts copyLink(href) (absolute URL on the current origin). Menus and sheets in Files, Recent, Notes explorer, the note view and Admin users now copy through it. Nothing calls navigator.clipboard directly any more.
  • Migrated: Settings sections and groups, the invitation link, Files header, selection bar, Info panel, Quick Look, share dialog, Shared by you, Calendar header, agenda days, item previews, and the Notes header. The phone Files action sheet uses menu-item for its Copy link rows.
  • Gates: bun run check gave 0 errors and 0 warnings. bun run test passed 126 of 126, including a new clipboard.test.ts. The e2e runs for files, calendar, notes and auth pass.
  • Before and after screenshots at 1440 and 390, Paper White and Tokyo Night: copylink-{settings,files,calendar}-{before,after}-*.png in the orchestrator's scratchpad.
Done on branch `ui-polish-73` (commit 5f7ef7c, plus be6ca07 for a name clash). Not pushed. - One `CopyLink` in `packages/ui/src/components/CopyLink.svelte` with `variant: 'pill' | 'segment' | 'menu-item'`. `CopyLinkAction`, `CopyLinkButton` and `CopyLinkSegment` are deleted. `index.ts` exports only `CopyLink` (plus `COPY_FEEDBACK_MS` and `CopyLinkVariant`). - The glyph swap is calternal.js DateDivider's copy to check, with its 500 ms duration (`COPY_FEEDBACK_MS`). - There is one announcement strategy: the control's own polite live region, or the caller's shared region through `onannounce`. Quick Look no longer shows its own toast on top of that. - There is one clipboard helper, `clipboard.ts` `copyLink(href)` (absolute URL on the current origin). Menus and sheets in Files, Recent, Notes explorer, the note view and Admin users now copy through it. Nothing calls `navigator.clipboard` directly any more. - Migrated: Settings sections and groups, the invitation link, Files header, selection bar, Info panel, Quick Look, share dialog, Shared by you, Calendar header, agenda days, item previews, and the Notes header. The phone Files action sheet uses `menu-item` for its Copy link rows. - Gates: `bun run check` gave 0 errors and 0 warnings. `bun run test` passed 126 of 126, including a new `clipboard.test.ts`. The e2e runs for files, calendar, notes and auth pass. - Before and after screenshots at 1440 and 390, Paper White and Tokyo Night: `copylink-{settings,files,calendar}-{before,after}-*.png` in the orchestrator's scratchpad.
Author
Owner

One current Copy link path bypasses the shared control: apps/web/src/routes/settings/admin/UsersGroup.svelte:96-100,160-163 renders a plain Pill and its own copy toast for a passkey re-enrolment URL. It does call the shared copyLink() helper. The CopyLink component also accepts absolute URLs, a pill variant, and callbacks for announcement and post-copy handling (packages/ui/src/components/CopyLink.svelte:40-53,69-80). Please include this surface in the existing CopyLink reuse review or record why its temporary passkey URL needs separate feedback.

One current Copy link path bypasses the shared control: `apps/web/src/routes/settings/admin/UsersGroup.svelte:96-100,160-163` renders a plain `Pill` and its own copy toast for a passkey re-enrolment URL. It does call the shared `copyLink()` helper. The `CopyLink` component also accepts absolute URLs, a pill variant, and callbacks for announcement and post-copy handling (`packages/ui/src/components/CopyLink.svelte:40-53,69-80`). Please include this surface in the existing CopyLink reuse review or record why its temporary passkey URL needs separate feedback.
Author
Owner

Consistency-hunt update from current dev at dfb5964a2fcf13dc8b9a50a319eee09bc386f322 (2026-09-30). apps/web/src/routes/settings/admin/UsersGroup.svelte:123 still calls the shared copyLink() helper directly and shows a local toast for the issued passkey URL. It does not use the shared CopyLink component, which owns the copy-to-check interaction and announcement behavior.

Rule: DESIGN §33 uses one Copy link control. Fix: add the needed absolute-URL or custom-label behavior to packages/ui/src/components/CopyLink.svelte and use that control here. Owner: packages/ui/src/components/CopyLink.svelte.

Consistency-hunt update from current `dev` at `dfb5964a2fcf13dc8b9a50a319eee09bc386f322` (2026-09-30). `apps/web/src/routes/settings/admin/UsersGroup.svelte:123` still calls the shared `copyLink()` helper directly and shows a local toast for the issued passkey URL. It does not use the shared `CopyLink` component, which owns the copy-to-check interaction and announcement behavior. Rule: DESIGN §33 uses one Copy link control. Fix: add the needed absolute-URL or custom-label behavior to `packages/ui/src/components/CopyLink.svelte` and use that control here. Owner: `packages/ui/src/components/CopyLink.svelte`.
Author
Owner

Hygiene review: UsersGroup.svelte still uses its local copy toast instead of the shared CopyLink control. Keeping #73 open for this remaining consumer.

Hygiene review: `UsersGroup.svelte` still uses its local copy toast instead of the shared `CopyLink` control. Keeping #73 open for this remaining consumer.
Author
Owner

Additional reuse evidence for #73 from rev-consistency (#427), origin/dev at c4a61e8cf090170f35b1bed3350d9de20c83ecd5:

The component and clipboard write consolidation is in place: packages/ui/src/components/CopyLink.svelte and packages/ui/src/clipboard.ts are single owners. The app's menu feedback is still repeated despite apps/web/src/lib/copyLinkToast.ts:9-12 owning the copy + success/failure toast mapping.

  • apps/web/src/lib/notes/NotesExplorer.svelte:266-296 repeats copy + toast handling for a Note row, two health searches and the convert view.
  • apps/web/src/lib/notifications/InboxPanel.svelte:136-140 repeats the same mapping with different failure wording.
  • apps/web/src/lib/files/RecentView.svelte:145-148 and apps/web/src/lib/files/FilesBrowser.svelte:679-687 repeat the mapping with an item-title success label.

Use copyLinkWithToast for app menu toasts. Extend it with a success-label option if needed so title-specific feedback survives. Leave CopyLink's check glyph/live-region feedback and package-only live-region callers with their existing owners. Test one clipboard call, one success or failure announcement and the preserved item title. The write helper itself is not duplicated. No new issue was filed because #73 already owns this reuse work.

Additional reuse evidence for #73 from rev-consistency (#427), `origin/dev` at `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`: The component and clipboard write consolidation is in place: `packages/ui/src/components/CopyLink.svelte` and `packages/ui/src/clipboard.ts` are single owners. The app's **menu feedback** is still repeated despite `apps/web/src/lib/copyLinkToast.ts:9-12` owning the copy + success/failure toast mapping. - `apps/web/src/lib/notes/NotesExplorer.svelte:266-296` repeats copy + toast handling for a Note row, two health searches and the convert view. - `apps/web/src/lib/notifications/InboxPanel.svelte:136-140` repeats the same mapping with different failure wording. - `apps/web/src/lib/files/RecentView.svelte:145-148` and `apps/web/src/lib/files/FilesBrowser.svelte:679-687` repeat the mapping with an item-title success label. Use `copyLinkWithToast` for app menu toasts. Extend it with a success-label option if needed so title-specific feedback survives. Leave CopyLink's check glyph/live-region feedback and package-only live-region callers with their existing owners. Test one clipboard call, one success or failure announcement and the preserved item title. The write helper itself is not duplicated. No new issue was filed because #73 already owns this reuse work.
Author
Owner

Already implemented on origin/dev. git log origin/dev --grep='(#73)' shows 5f7ef7ce8, which adds one CopyLink control and removes CopyLinkAction, CopyLinkButton and CopyLinkSegment. Current packages/ui/src/components/CopyLink.svelte exposes the pill, segment and menu-item variants, and packages/ui/src/index.ts exports that one control. Recommend recording the reuse fix here. Do not close the issue in this audit.

Already implemented on origin/dev. git log origin/dev --grep='(#73)' shows 5f7ef7ce8, which adds one CopyLink control and removes CopyLinkAction, CopyLinkButton and CopyLinkSegment. Current packages/ui/src/components/CopyLink.svelte exposes the pill, segment and menu-item variants, and packages/ui/src/index.ts exports that one control. Recommend recording the reuse fix here. Do not close the issue in this audit.
Author
Owner

Fixed in 5f7ef7ce8 (origin/dev); covered by apps/web/src/lib/clipboard.test.ts and the shared CopyLink.svelte implementation.

Fixed in 5f7ef7ce8 (origin/dev); covered by `apps/web/src/lib/clipboard.test.ts` and the shared `CopyLink.svelte` implementation.
kayg closed this issue 2026-10-03 11:55:19 +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#73
No description provided.