Tags: drag-to-merge needs drag, drop-target and drop states #1187

Open
opened 2026-10-06 07:35:17 +00:00 by kayg · 25 comments
Owner

Owner report (2026-10-06): "dragging one tag to another has no drag state / preview and no drop state / preview"

Dragging a sidebar Tag onto another Tag (merge) must show:

  • Drag: the dragged row lifts (shared drag ghost: the Tag chip with its colour dot and count), the source row dims in place.
  • Drop target: hovering another Tag highlights it with the shared drop-target style and a short label "Merge into #target"; invalid targets (itself, a child into its own parent if that is invalid) show no highlight and a not-allowed cursor.
  • Drop: a confirmation popover "Merge #a into #b? 21 items · You can Undo" (existing merge confirm), then the optimistic result.
  • Keyboard: a "Merge into…" menu action with a Tag picker (same result). Touch: long-press drag with the same states.
    Reuse the shared drag primitives (Kanban #1118 / Notes tree) — no bespoke drag code. e2e like a User with pointer and touch; screenshots of drag, hover-target and drop states at 1440 and 390, light + dark, reviewed at ACTUAL size.
## Owner report (2026-10-06): "dragging one tag to another has no drag state / preview and no drop state / preview" Dragging a sidebar Tag onto another Tag (merge) must show: - **Drag:** the dragged row lifts (shared drag ghost: the Tag chip with its colour dot and count), the source row dims in place. - **Drop target:** hovering another Tag highlights it with the shared drop-target style and a short label "Merge into #target"; invalid targets (itself, a child into its own parent if that is invalid) show no highlight and a not-allowed cursor. - **Drop:** a confirmation popover "Merge #a into #b? 21 items · You can Undo" (existing merge confirm), then the optimistic result. - Keyboard: a "Merge into…" menu action with a Tag picker (same result). Touch: long-press drag with the same states. Reuse the shared drag primitives (Kanban #1118 / Notes tree) — no bespoke drag code. e2e like a User with pointer and touch; screenshots of drag, hover-target and drop states at 1440 and 390, light + dark, reviewed at ACTUAL size.
Author
Owner

Starting work on job/tagdnd-1187 from base f5fbced3c7. origin/dev is 5301e02085 (branch is 3 commits behind); I will incorporate origin/dev once before final gates.

Starting work on job/tagdnd-1187 from base f5fbced3c733370b5b37544d7816732628be2fd0. origin/dev is 5301e020859c1d61b4a7f812a5049a64241203e8 (branch is 3 commits behind); I will incorporate origin/dev once before final gates.
Author
Owner

Finding: the first Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Keyboard focus rings use the shared focus tokens.
UI transitions and animation options use shared motion tokens or documented exceptions. stopped before source validation because this worktree has no node_modules (license check: 'node_modules not found'; perf-lint could not load TypeScript; svelte-kit and svelte-check were unavailable). I am installing the existing locked workspace dependencies with --frozen-lockfile, then I will rerun the gate.

Finding: the first Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Keyboard focus rings use the shared focus tokens. UI transitions and animation options use shared motion tokens or documented exceptions. stopped before source validation because this worktree has no node_modules (license check: 'node_modules not found'; perf-lint could not load TypeScript; svelte-kit and svelte-check were unavailable). I am installing the existing locked workspace dependencies with --frozen-lockfile, then I will rerun the gate.
Author
Owner

Finding: the first web check stopped before source validation because this worktree had no node_modules. The license check reported “node_modules not found”, perf-lint could not load TypeScript, and svelte-kit/svelte-check were unavailable. I installed the existing locked workspace dependencies with bun install --frozen-lockfile; no dependency was added.

Finding: the first web check stopped before source validation because this worktree had no node_modules. The license check reported “node_modules not found”, perf-lint could not load TypeScript, and svelte-kit/svelte-check were unavailable. I installed the existing locked workspace dependencies with `bun install --frozen-lockfile`; no dependency was added.
Author
Owner

Finding: TagRow.svelte already had a Merge into… action and the durable rewrite confirmation, but tagDrag.svelte.ts only tracked a pointer target. It did not create the shared lifted clone or reject self/descendant targets before highlighting. The Kanban zone in TaskColumn.svelte uses the vendored svelte-dnd-action; I am extending that primitive for Tag rows. The server rename validator rejects moving a Tag below its own prefix, so the UI now applies the same boundary check before showing a valid target.

Finding: `TagRow.svelte` already had a Merge into… action and the durable rewrite confirmation, but `tagDrag.svelte.ts` only tracked a pointer target. It did not create the shared lifted clone or reject self/descendant targets before highlighting. The Kanban zone in `TaskColumn.svelte` uses the vendored `svelte-dnd-action`; I am extending that primitive for Tag rows. The server rename validator rejects moving a Tag below its own prefix, so the UI now applies the same boundary check before showing a valid target.
Author
Owner

Real-server e2e found that the compact TagPill drag ghost did not reliably activate a target when hit testing used the row clone center. The shared DnD zone now uses pointer hit testing; it keeps the shared ghost/zone and highlights the hovered valid Tag. Production pointer and held-touch flows now pass, including keyboard merge, 21-item confirmation, merge and Undo.

Real-server e2e found that the compact TagPill drag ghost did not reliably activate a target when hit testing used the row clone center. The shared DnD zone now uses pointer hit testing; it keeps the shared ghost/zone and highlights the hovered valid Tag. Production pointer and held-touch flows now pass, including keyboard merge, 21-item confirmation, merge and Undo.
Author
Owner

Real-server touch screenshot testing exposed a mobile confirmation hit-testing defect: during the 390 px sidebar flow, the shared ConfirmSheet surface and Cancel button inherited pointer-events: none from the body lock used by the sidebar Dialog. document.elementFromPoint() hit the Tag row behind Cancel. I added an explicit hit-testing reset to the shared OverlaySurface scrim and content surface. The production build and phone Cancel regression check are running now.

Real-server touch screenshot testing exposed a mobile confirmation hit-testing defect: during the 390 px sidebar flow, the shared ConfirmSheet surface and Cancel button inherited `pointer-events: none` from the body lock used by the sidebar Dialog. `document.elementFromPoint()` hit the Tag row behind Cancel. I added an explicit hit-testing reset to the shared OverlaySurface scrim and content surface. The production build and phone Cancel regression check are running now.
Author
Owner

Decision where DESIGN §§33, 34 and 59 are silent: reject a Tag drop onto itself or a descendant (for example, area/work → area/work/sub) to avoid a namespace prefix cycle. Allow a child Tag to merge into its parent (area/work/sub → area/work), which collapses one namespace level. The DnD adapter uses the shared primitive's cursor hit testing because the shared TagPill ghost is compact; it does not add a custom drop-zone implementation.

Decision where DESIGN §§33, 34 and 59 are silent: reject a Tag drop onto itself or a descendant (for example, `area/work` → `area/work/sub`) to avoid a namespace prefix cycle. Allow a child Tag to merge into its parent (`area/work/sub` → `area/work`), which collapses one namespace level. The DnD adapter uses the shared primitive's cursor hit testing because the shared TagPill ghost is compact; it does not add a custom drop-zone implementation.
Author
Owner

Mac-platform production screenshots for #1187. The browser uses macOS platform emulation. Input: 390 px and 820 px held touch; 1440 px pointer. Build ID: c9feb4ed30.

Mac-platform production screenshots for #1187. The browser uses macOS platform emulation. Input: 390 px and 820 px held touch; 1440 px pointer. Build ID: c9feb4ed302894860d4985cfc42be0000df56027. - light, 390 px (touch): [drag](https://git.kayg.org/attachments/563ec8d3-5332-4351-892a-8e07b5af553d) · [valid target](https://git.kayg.org/attachments/f6abf7b7-6bd2-4c15-997c-e4a1d177dd7f) · [confirmation](https://git.kayg.org/attachments/1aebb902-4491-46df-ada9-5227170dd478) - light, 820 px (touch): [drag](https://git.kayg.org/attachments/f3b39d48-ab16-4e91-9457-4208fb622499) · [valid target](https://git.kayg.org/attachments/e9437f5c-ba11-4c2f-8ef6-285de94c1b94) · [confirmation](https://git.kayg.org/attachments/7ee8c70d-9f99-4d27-83c1-36cf84f2902a) - light, 1440 px (pointer): [drag](https://git.kayg.org/attachments/43650bac-e178-4e0d-9a9a-0e5c64c68e56) · [valid target](https://git.kayg.org/attachments/023e911b-5acd-4cd5-a983-89c9efb9aa92) · [confirmation](https://git.kayg.org/attachments/e2f30b48-1289-48e8-aaff-b03c67c77876) - dark, 390 px (touch): [drag](https://git.kayg.org/attachments/2e81fcfd-b2c1-404c-a9b5-f18e1115bbbc) · [valid target](https://git.kayg.org/attachments/a5c5c49a-d32d-4558-a484-8ee3b6d3f6cc) · [confirmation](https://git.kayg.org/attachments/6ef56c5e-e0bd-458e-ac21-eb2296aa8e85) - dark, 820 px (touch): [drag](https://git.kayg.org/attachments/f6138c9d-2d8a-4bb2-9d09-25ba1f6ca417) · [valid target](https://git.kayg.org/attachments/0269a8f7-1653-4229-a1fe-81c51e02737e) · [confirmation](https://git.kayg.org/attachments/e05de0fb-b13f-4e20-a2e6-0c43e7745432) - dark, 1440 px (pointer): [drag](https://git.kayg.org/attachments/aed13514-8967-418c-ba4f-5b64d9177289) · [valid target](https://git.kayg.org/attachments/77e07b3d-9e61-4605-b9aa-90b3af282d07) · [confirmation](https://git.kayg.org/attachments/f9dea6cf-0ac8-4018-8e74-5e58a839e490)
Author
Owner

The mobile confirmation hit-testing issue is fixed and verified in the production build. The 390 px and 820 px screenshot flows now hit the real Cancel button, and the real-server acceptance flow passes keyboard merge, pointer merge, held-touch merge and Undo. The run reported zero CSP errors across its two pages.

The mobile confirmation hit-testing issue is fixed and verified in the production build. The 390 px and 820 px screenshot flows now hit the real Cancel button, and the real-server acceptance flow passes keyboard merge, pointer merge, held-touch merge and Undo. The run reported zero CSP errors across its two pages.
Author
Owner

#1187 complete

Implemented sidebar Tag drag-to-merge using the shared Kanban DnD action. Final branch head: 9994f807184a20f3bf25ce2fcd700be4557dfeb4.

What changed

  • A pointer or 450 ms long-press drag lifts a shared TagPill ghost with its colour dot and item count. The source stays in place and dims.
  • A valid target gets the shared drop outline and Merge into #target label. Invalid self and parent-to-descendant drops show no highlight and use a not-allowed cursor.
  • Drop opens the existing confirmation, then uses the existing Tag rewrite worker and Undo. The sidebar menu has a searchable “Merge into…” picker.
  • Shared DnD zones register only for the visible writable sidebar tree. Each row keeps one real item and at most one shadow item.
  • Fixed nested confirmation hit-testing on mobile by restoring pointer input on the shared overlay scrim and surface.

Shared components

Reused the vendored shared dndzone, TagPill, Pill, Menu, TextInput, ConfirmSheet, OverlaySurface, ItemActionMenu, RequestState, and shared DnD token classes. No feature-local drop paint or drag library was added.

Files

  • apps/web/src/lib/actions/tagDrag.svelte.ts and its Vitest file
  • apps/web/src/lib/components/TagRow.svelte, its Vitest file, and app-sidebar.svelte
  • apps/web/src/lib/tasks/TasksSidebar.svelte and TaskColumn.svelte
  • apps/web/e2e/tags-1110-1111.mjs
  • apps/web/src/lib/components/TagPill.svelte.test.ts
  • packages/ui/src/components/TagPill.svelte, OverlaySurface.svelte, tokens.css, and README.md
  • contracts/perf/adoption-1058.json, contracts/perf/exceptions.json, bench/tag-dnd-1187.mjs, and docs/perf/baseline.json

UX gaps closed

Pointer, touch, and keyboard paths use the same merge result. The real-server flow verified confirmation, durable writes, and Undo. The Tag picker uses real API data and has loading, error/retry, empty, and no-match states. Sidebar targets retain 44 px touch height. Screenshots cover drag, target hover, and confirmation at 390, 820, and 1440 px in light and dark with macOS platform emulation; 390/820 use touch and 1440 uses pointer. The 18 screenshots are attached in the earlier evidence comment.

UX gaps left

No feature behavior gap found in the exercised flows. Native Mac GUI review was not run; the attached screenshots use the required macOS emulation and remain for the orchestrator’s visual review.

Decisions

DESIGN does not define invalid namespace merges. A Tag cannot merge into itself or its descendant because that would create a prefix cycle; a child Tag can merge into its parent. The shared DnD hit test follows the pointer so the compact TagPill ghost does not change which row is targeted.

Performance

Expected drag work is O(N) registered zones per shared-library observation pass, where N is the number of rendered writable sidebar Tags; a merge preview tracks one source and one target. bench/tag-dnd-1187.mjs seeds real indexed Notes, then measures 20-Tag average and 500-Tag burst workloads. The local run reported average target-hover p50/p95 187.9/459.7 ms and worst-case 225.5/637.1 ms; warm activation p50/p95 was 355.8/672.2 ms and 818.8/1912.3 ms. At 500 Tags, browser mean/peak RSS was 760,753,752/802,746,368 bytes and server mean/peak RSS was 460,697,753/463,794,176 bytes.

There is no controlled prior or perf-VM baseline. The local host load average was 22.99, 40.54, 47.82 and the perf-VM lock was held when checked after the run. These values are directional only and are recorded as such in docs/perf/baseline.json.

Gates and evidence

apps/web bun run check passed. Relevant output, verbatim:

PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages
perf-lint: PASS; 0 violations; 22359 scoped exceptions
User browser caches use userStorage; only documented device/public-link exceptions remain.
Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Keyboard focus rings use the shared focus tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
svelte-check found 0 errors and 2 warnings in 2 files

The two warnings are empty CSS rulesets in AttachmentDeck.svelte and AgendaList.svelte.

bun run test -- --maxWorkers=2 passed. Summary, verbatim:

 Test Files  275 passed (275)
      Tests  1910 passed (1910)
   Start at  14:10:17
   Duration  680.40s (transform 34%, import 24%, environment 24%, tests 14%, setup 4%)

Real-server pointer, touch, keyboard, confirmation, and Undo flow, verbatim:

PASS durable Tag route boundaries
STAGE keyboard merge
STAGE sidebar pointer merge
STAGE sidebar touch merge
PASS tag rename, merge, Undo and touch flows
CSP REPORTS tags-1110-1111: 0 across 2 pages

No Rust source changed, so Rust format, clippy, and test gates were not run. cargo clean completed with Removed 8633 files, 7.3GiB total; apps/web/build and temporary local server copies were removed.

## #1187 complete Implemented sidebar Tag drag-to-merge using the shared Kanban DnD action. Final branch head: `9994f807184a20f3bf25ce2fcd700be4557dfeb4`. ### What changed - A pointer or 450 ms long-press drag lifts a shared TagPill ghost with its colour dot and item count. The source stays in place and dims. - A valid target gets the shared drop outline and `Merge into #target` label. Invalid self and parent-to-descendant drops show no highlight and use a not-allowed cursor. - Drop opens the existing confirmation, then uses the existing Tag rewrite worker and Undo. The sidebar menu has a searchable “Merge into…” picker. - Shared DnD zones register only for the visible writable sidebar tree. Each row keeps one real item and at most one shadow item. - Fixed nested confirmation hit-testing on mobile by restoring pointer input on the shared overlay scrim and surface. ### Shared components Reused the vendored shared `dndzone`, `TagPill`, `Pill`, `Menu`, `TextInput`, `ConfirmSheet`, `OverlaySurface`, `ItemActionMenu`, `RequestState`, and shared DnD token classes. No feature-local drop paint or drag library was added. ### Files - `apps/web/src/lib/actions/tagDrag.svelte.ts` and its Vitest file - `apps/web/src/lib/components/TagRow.svelte`, its Vitest file, and `app-sidebar.svelte` - `apps/web/src/lib/tasks/TasksSidebar.svelte` and `TaskColumn.svelte` - `apps/web/e2e/tags-1110-1111.mjs` - `apps/web/src/lib/components/TagPill.svelte.test.ts` - `packages/ui/src/components/TagPill.svelte`, `OverlaySurface.svelte`, `tokens.css`, and `README.md` - `contracts/perf/adoption-1058.json`, `contracts/perf/exceptions.json`, `bench/tag-dnd-1187.mjs`, and `docs/perf/baseline.json` ### UX gaps closed Pointer, touch, and keyboard paths use the same merge result. The real-server flow verified confirmation, durable writes, and Undo. The Tag picker uses real API data and has loading, error/retry, empty, and no-match states. Sidebar targets retain 44 px touch height. Screenshots cover drag, target hover, and confirmation at 390, 820, and 1440 px in light and dark with macOS platform emulation; 390/820 use touch and 1440 uses pointer. The 18 screenshots are attached in the earlier evidence comment. ### UX gaps left No feature behavior gap found in the exercised flows. Native Mac GUI review was not run; the attached screenshots use the required macOS emulation and remain for the orchestrator’s visual review. ### Decisions DESIGN does not define invalid namespace merges. A Tag cannot merge into itself or its descendant because that would create a prefix cycle; a child Tag can merge into its parent. The shared DnD hit test follows the pointer so the compact TagPill ghost does not change which row is targeted. ### Performance Expected drag work is O(N) registered zones per shared-library observation pass, where N is the number of rendered writable sidebar Tags; a merge preview tracks one source and one target. `bench/tag-dnd-1187.mjs` seeds real indexed Notes, then measures 20-Tag average and 500-Tag burst workloads. The local run reported average target-hover p50/p95 187.9/459.7 ms and worst-case 225.5/637.1 ms; warm activation p50/p95 was 355.8/672.2 ms and 818.8/1912.3 ms. At 500 Tags, browser mean/peak RSS was 760,753,752/802,746,368 bytes and server mean/peak RSS was 460,697,753/463,794,176 bytes. There is no controlled prior or perf-VM baseline. The local host load average was 22.99, 40.54, 47.82 and the perf-VM lock was held when checked after the run. These values are directional only and are recorded as such in `docs/perf/baseline.json`. ### Gates and evidence `apps/web` `bun run check` passed. Relevant output, verbatim: ```text PASS production dependency licences: 759 locked package releases across apps/web, apps/docs and packages perf-lint: PASS; 0 violations; 22359 scoped exceptions User browser caches use userStorage; only documented device/public-link exceptions remain. Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Keyboard focus rings use the shared focus tokens. UI transitions and animation options use shared motion tokens or documented exceptions. svelte-check found 0 errors and 2 warnings in 2 files ``` The two warnings are empty CSS rulesets in `AttachmentDeck.svelte` and `AgendaList.svelte`. `bun run test -- --maxWorkers=2` passed. Summary, verbatim: ```text Test Files 275 passed (275) Tests 1910 passed (1910) Start at 14:10:17 Duration 680.40s (transform 34%, import 24%, environment 24%, tests 14%, setup 4%) ``` Real-server pointer, touch, keyboard, confirmation, and Undo flow, verbatim: ```text PASS durable Tag route boundaries STAGE keyboard merge STAGE sidebar pointer merge STAGE sidebar touch merge PASS tag rename, merge, Undo and touch flows CSP REPORTS tags-1110-1111: 0 across 2 pages ``` No Rust source changed, so Rust format, clippy, and test gates were not run. `cargo clean` completed with `Removed 8633 files, 7.3GiB total`; `apps/web/build` and temporary local server copies were removed.
Author
Owner

Started #1187 on branch job/tagdnd-1187.

Branch head: 9994f80718
Current origin/dev: 3601719b80
Branch base (merge-base): 5301e02085

I am tracing the current sidebar Tag hover path, then I will coalesce pointer work to one animation frame and cache target geometry. I will record three interleaved Chrome trace runs at 20 and 500 Tags before and after, along with the web gates.

Started #1187 on branch job/tagdnd-1187. Branch head: 9994f807184a20f3bf25ce2fcd700be4557dfeb4 Current origin/dev: 3601719b80a39f712daf07b37879b22929dfffa0 Branch base (merge-base): 5301e020859c1d61b4a7f812a5049a64241203e8 I am tracing the current sidebar Tag hover path, then I will coalesce pointer work to one animation frame and cache target geometry. I will record three interleaved Chrome trace runs at 20 and 500 Tags before and after, along with the web gates.
Author
Owner

Finding: tagDrag sets useCursorForDetection, but the shared pointer observer still schedules repeated zone scans. With 500 registered Tag zones, observe() checks zones in sequence and findWouldBeIndex() reads geometry while searching; its minimum polling interval is 20 ms when the zone animation duration is zero. The hot path is in apps/web/src/lib/vendor/svelte-dnd-action/index.mjs (observe, watchDraggedElement, findWouldBeIndex). I am adding an opt-in cursor hit-test path for Tag zones and making Tag shadow/state publication idempotent.

Finding: `tagDrag` sets `useCursorForDetection`, but the shared pointer observer still schedules repeated zone scans. With 500 registered Tag zones, `observe()` checks zones in sequence and `findWouldBeIndex()` reads geometry while searching; its minimum polling interval is 20 ms when the zone animation duration is zero. The hot path is in `apps/web/src/lib/vendor/svelte-dnd-action/index.mjs` (`observe`, `watchDraggedElement`, `findWouldBeIndex`). I am adding an opt-in cursor hit-test path for Tag zones and making Tag shadow/state publication idempotent.
Author
Owner

Implementation commits:

  • bb9cdf2d6 adds the opt-in frame-coalesced elementFromPoint path for Tag zones and idempotent row publication.
  • ffe5c28e3 adds interleaved paired Chrome traces at 20 and 500 Tags, with pointermove dispatch/script slices, long tasks, INP candidates and process metrics.

Focused regression: bunx vitest run src/lib/actions/tagDrag.svelte.test.ts --maxWorkers=2 — 5 passed. I am building the local server and production frontend to run the pointer/touch flow checks and paired traces.

Implementation commits: - `bb9cdf2d6` adds the opt-in frame-coalesced elementFromPoint path for Tag zones and idempotent row publication. - `ffe5c28e3` adds interleaved paired Chrome traces at 20 and 500 Tags, with pointermove dispatch/script slices, long tasks, INP candidates and process metrics. Focused regression: `bunx vitest run src/lib/actions/tagDrag.svelte.test.ts --maxWorkers=2` — 5 passed. I am building the local server and production frontend to run the pointer/touch flow checks and paired traces.
Author
Owner

After the origin/dev merge, bun run check found stale performance hashes for live render scopes in the changed Tag sidebar and task rows. I used the repository exact-scope refresh workflow: it updated 82 hashes across app-sidebar.svelte, TasksSidebar.svelte, TaskColumn.svelte, TagPill.svelte and tokens.css; it added and removed no scopes and left the ratchet unchanged. scripts/perf-lint --adopt-7c completed with perf-lint: PASS; 0 violations; 22548 scoped exceptions.

After the `origin/dev` merge, `bun run check` found stale performance hashes for live render scopes in the changed Tag sidebar and task rows. I used the repository exact-scope refresh workflow: it updated 82 hashes across `app-sidebar.svelte`, `TasksSidebar.svelte`, `TaskColumn.svelte`, `TagPill.svelte` and `tokens.css`; it added and removed no scopes and left the ratchet unchanged. `scripts/perf-lint --adopt-7c` completed with `perf-lint: PASS; 0 violations; 22548 scoped exceptions`.
Author
Owner

E2E finding: the Tag row ⋯ click timed out because the DnD clone's data-tag-drag-standard wrapper intercepted pointer hits intended for the menu button. I added display: contents for that wrapper, which keeps its children as hit targets without adding a live layout box. The focused Tag drag and TagRow tests pass (11/11). I am rebuilding the matching server binary before rerunning the production E2E flow.

E2E finding: the Tag row ⋯ click timed out because the DnD clone's `data-tag-drag-standard` wrapper intercepted pointer hits intended for the menu button. I added `display: contents` for that wrapper, which keeps its children as hit targets without adding a live layout box. The focused Tag drag and TagRow tests pass (11/11). I am rebuilding the matching server binary before rerunning the production E2E flow.
Author
Owner

Touch regression found in the production E2E: the held-touch merge did not set the target row's .ui-dnd-drop-target state within 30 seconds, while pointer flows passed. The fast path skipped touchmove whenever the browser exposed PointerEvent, so touch coordinates could not reach the shared hit-test frame if that backend supplied touch events without a matching pointermove. I am keeping touch coordinates on the same rAF scheduler; it will still coalesce to one target lookup per frame.

Touch regression found in the production E2E: the held-touch merge did not set the target row's `.ui-dnd-drop-target` state within 30 seconds, while pointer flows passed. The fast path skipped `touchmove` whenever the browser exposed `PointerEvent`, so touch coordinates could not reach the shared hit-test frame if that backend supplied touch events without a matching pointermove. I am keeping touch coordinates on the same rAF scheduler; it will still coalesce to one target lookup per frame.
Author
Owner

Full E2E reached the Task Tag action after the pointer and touch merge screenshots passed. Playwright then showed the menu button was intercepted by the Tag drag preview (.tag-filter-dot). TagRow sets the preview to display:inline-flex, which overrides the browser's default [hidden] rule, so the supposedly hidden preview remained in the live row. I am adding an explicit [hidden] { display:none } rule; the clone still reveals the preview when dragging.

Full E2E reached the Task Tag action after the pointer and touch merge screenshots passed. Playwright then showed the menu button was intercepted by the Tag drag preview (`.tag-filter-dot`). `TagRow` sets the preview to `display:inline-flex`, which overrides the browser's default `[hidden]` rule, so the supposedly hidden preview remained in the live row. I am adding an explicit `[hidden] { display:none }` rule; the clone still reveals the preview when dragging.
Author
Owner

Progress (head 83e96bdff): the hidden Tag drag preview had display: inline-flex, which overrode the browser's [hidden] default and left an invisible element in pointer hit testing. I fixed the shared preview CSS in 13eacb66c. The rename E2E now hovers the row before clicking its hover-revealed action, matching the actual fine-pointer interaction.

The drag performance path now coalesces pointer input to one rAF lookup and uses elementFromPoint plus registered-zone ancestor checks; it does not read every Tag rect on pointermove. A full pointer merge still needs a clean end-to-end pass. The perf VM remains held by tabswitch-1233; the before/after 20- and 500-Tag Chrome trace run is pending its release. No performance result is claimed yet.

Progress (head `83e96bdff`): the hidden Tag drag preview had `display: inline-flex`, which overrode the browser's `[hidden]` default and left an invisible element in pointer hit testing. I fixed the shared preview CSS in `13eacb66c`. The rename E2E now hovers the row before clicking its hover-revealed action, matching the actual fine-pointer interaction. The drag performance path now coalesces pointer input to one rAF lookup and uses `elementFromPoint` plus registered-zone ancestor checks; it does not read every Tag rect on pointermove. A full pointer merge still needs a clean end-to-end pass. The perf VM remains held by `tabswitch-1233`; the before/after 20- and 500-Tag Chrome trace run is pending its release. No performance result is claimed yet.
Author
Owner

Pointer-flow finding on #1187: a production E2E run at head 83e96bdff completed the rename and keyboard merge stages, then timed out waiting 15 seconds for #dnd-action-dragged-el at the sidebar pointer merge. A subsequent pointer diagnostic created the clone once but did not observe target feedback. The focused held-touch flow passes. The host load was above 60 during concurrent browser jobs, so I am treating these results as load-contaminated until a clean run confirms or clears the pointer failure; I have not changed any test expectation.

Pointer-flow finding on #1187: a production E2E run at head `83e96bdff` completed the rename and keyboard merge stages, then timed out waiting 15 seconds for `#dnd-action-dragged-el` at the sidebar pointer merge. A subsequent pointer diagnostic created the clone once but did not observe target feedback. The focused held-touch flow passes. The host load was above 60 during concurrent browser jobs, so I am treating these results as load-contaminated until a clean run confirms or clears the pointer failure; I have not changed any test expectation.
Author
Owner

The first local Chrome profile did not produce performance metrics. With host load averages 51.71 / 52.26 / 47.57, Playwright timed out while waiting for a Tag row to stabilize during an active drag (scrollIntoViewIfNeeded, 26.8 s); no before/after trace was recorded. The benchmark now chooses already-visible Tag rows and avoids scrolling during the drag. I also made trace and /proc samplers stop before Browser/server teardown so a failed sample cannot obscure the interaction failure. These harness fixes are committed as c33d17a24 and aba9ebc9e; I am rerunning the paired profile locally because the perf VM remains locked.

The first local Chrome profile did not produce performance metrics. With host load averages 51.71 / 52.26 / 47.57, Playwright timed out while waiting for a Tag row to stabilize during an active drag (`scrollIntoViewIfNeeded`, 26.8 s); no before/after trace was recorded. The benchmark now chooses already-visible Tag rows and avoids scrolling during the drag. I also made trace and `/proc` samplers stop before Browser/server teardown so a failed sample cannot obscure the interaction failure. These harness fixes are committed as `c33d17a24` and `aba9ebc9e`; I am rerunning the paired profile locally because the perf VM remains locked.
Author
Owner

The next profile attempt also stopped before measurement: the installed Playwright Locator did not expose isInViewport() (TypeError in preparePage), so no profile or traces were produced. I replaced it with one-time row, sidebar scrollport and window-bound checks during fixture setup. This is committed as 9eb9c46df; the application code and target geometry are unchanged.

The next profile attempt also stopped before measurement: the installed Playwright Locator did not expose `isInViewport()` (`TypeError` in `preparePage`), so no profile or traces were produced. I replaced it with one-time row, sidebar scrollport and window-bound checks during fixture setup. This is committed as `9eb9c46df`; the application code and target geometry are unchanged.
Author
Owner

The third local profile produced one trace, then stopped before the paired after run. At 20 Tags on the unoptimized before build, the measured trace had 35 pointermove events and 35 frames; frame scripting p50/p95/max was 7.11 / 461.10 / 811.92 ms, long tasks over 50 ms: 118 (maximum 5,993 ms), and INP candidate: 10,632 ms. Host load rose from 49.61 / 50.00 / 49.25 to 59.09 / 57.64 / 53.14 while several other browser and Rust jobs ran. Target feedback did not arrive within 10 seconds. This is load-contaminated and is not an after result or a baseline; I am moving the paired run to the perf VM when its current lock releases.

The third local profile produced one trace, then stopped before the paired `after` run. At 20 Tags on the unoptimized `before` build, the measured trace had 35 pointermove events and 35 frames; frame scripting p50/p95/max was 7.11 / 461.10 / 811.92 ms, long tasks over 50 ms: 118 (maximum 5,993 ms), and INP candidate: 10,632 ms. Host load rose from 49.61 / 50.00 / 49.25 to 59.09 / 57.64 / 53.14 while several other browser and Rust jobs ran. Target feedback did not arrive within 10 seconds. This is load-contaminated and is not an after result or a baseline; I am moving the paired run to the perf VM when its current lock releases.
Author
Owner

Perf verification finding (2026-10-07): the first perf-run exited before any timing sample. Server startup returned DecoderRejected / NotFound because the staged media runtime carried Nix store bindings that are absent on the Debian perf VM. I rebuilt the staged runtime around that host's /usr/bin media tools and removed the Nix-only bindings. A runtime check queued behind tabswitch-1233's 60-minute perf reservation and was cancelled before execution. One local fallback began at load average 42.55/44.63/46.53; it timed out waiting for the first target after recording one trace, so it is not a valid before/after profile. No performance result is claimed yet.

Perf verification finding (2026-10-07): the first perf-run exited before any timing sample. Server startup returned `DecoderRejected` / `NotFound` because the staged media runtime carried Nix store bindings that are absent on the Debian perf VM. I rebuilt the staged runtime around that host's `/usr/bin` media tools and removed the Nix-only bindings. A runtime check queued behind `tabswitch-1233`'s 60-minute perf reservation and was cancelled before execution. One local fallback began at load average `42.55/44.63/46.53`; it timed out waiting for the first target after recording one trace, so it is not a valid before/after profile. No performance result is claimed yet.
Author
Owner

Perf update (2026-10-07): after replacing Nix-only staged media tools with the perf VM's /usr/bin tools, the next locked server start still exited before measurements with DecoderRejected / NotFound. The lock then briefly released and tabstorm-1338 reserved it at 11:24:41 CEST for 35 minutes, ending at the four-hour job cutoff. I cancelled my queued runtime probe. The local attempt at load average 44.99/45.08/46.66 recorded one average-before trace and timed out before a paired run. No valid 20/500 before/after profile or p95 claim is available; docs/perf/baseline.json remains unchanged.

Perf update (2026-10-07): after replacing Nix-only staged media tools with the perf VM's `/usr/bin` tools, the next locked server start still exited before measurements with `DecoderRejected` / `NotFound`. The lock then briefly released and `tabstorm-1338` reserved it at 11:24:41 CEST for 35 minutes, ending at the four-hour job cutoff. I cancelled my queued runtime probe. The local attempt at load average `44.99/45.08/46.66` recorded one `average-before` trace and timed out before a paired run. No valid 20/500 before/after profile or p95 claim is available; `docs/perf/baseline.json` remains unchanged.
Author
Owner

Forgejo #1187 — final report

Branch: job/tagdnd-1187
Head: 74204854893b3e340f5d40967c82b0c67231c2dd

Built

  • Tag pointer and held-touch lookup now coalesces movement through one requestAnimationFrame callback. The callback uses elementFromPoint and registered-zone ancestor membership, then checks only the hit zone's one-item list. Pointer-up flushes the last queued point.
  • Target and item state publication is idempotent. A pointer staying over the same Tag does not write reactive state.
  • The hidden live-row preview is removed from hit testing. Shared TagPill, OverlaySurface, ConfirmSheet, ItemActionMenu, Pill, Menu and TextInput remain the visible controls.
  • The benchmark profile creates real 20- and 500-Tag Instances and records three paired before/after runs, Chrome traces, pointermove dispatch and scripting, frame scripting, long tasks, INP candidates, CPU and RSS.

Expected cost: register O(N) Tag zones once at drag start. Each pointer frame then walks the DOM ancestor chain and checks one target zone with one item. It does not read every zone's rectangle or publish state while the target is unchanged.

Files

  • apps/web/e2e/tags-1110-1111.mjs
  • apps/web/src/lib/actions/tagDrag.svelte.test.ts
  • apps/web/src/lib/actions/tagDrag.svelte.ts
  • apps/web/src/lib/components/TagPill.svelte.test.ts
  • apps/web/src/lib/components/TagRow.svelte
  • apps/web/src/lib/components/TagRow.svelte.test.ts
  • apps/web/src/lib/components/app-sidebar.svelte
  • apps/web/src/lib/tasks/TaskColumn.svelte
  • apps/web/src/lib/tasks/TasksSidebar.svelte
  • apps/web/src/lib/vendor/svelte-dnd-action/index.d.ts
  • apps/web/src/lib/vendor/svelte-dnd-action/index.mjs
  • bench/tag-dnd-1187.mjs
  • contracts/perf/adoption-1058.json
  • contracts/perf/exceptions.json
  • packages/ui/README.md
  • packages/ui/src/components/OverlaySurface.svelte
  • packages/ui/src/components/TagPill.svelte
  • packages/ui/src/tokens.css

Gates

cd apps/web && bun run check:

perf-lint: PASS; 0 violations; 22548 scoped exceptions
User browser caches use userStorage; only documented device/public-link exceptions remain.
Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css.
Text sizes and UI shape values use shared role tokens.
Keyboard focus rings use the shared focus tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/tagdnd-1187/apps/web
Getting Svelte diagnostics...

/home/kayg/Developer/calternal-wt/tagdnd-1187/apps/web/../../packages/ui/src/components/calendar/AttachmentDeck.svelte:1055:3
Warn: Do not use empty rulesets (css)
  }
  .attachment-row:focus {

====================================
svelte-check found 0 errors and 1 warning in 1 file

bun run test -- --maxWorkers=2:

 Test Files  12 failed | 276 passed (288)
      Tests  12 failed | 2026 passed (2038)
   Start at  08:59:51
   Duration  2128.13s (transform 31%, environment 27%, import 21%, tests 14%, setup 7%)

Environment  |component| jsdom was created 115 times · 965.21s total, 31% 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#caching-between-reruns

error: script "test" exited with code 1

The 12 failures were 5-second timeouts in unrelated Settings tests under shared-host load. The focused Tag suites passed:

 Test Files  3 passed (3)
      Tests  12 passed (12)
   Start at  09:35:57
   Duration  92.79s (transform 78%, import 8%, environment 7%, setup 3%, tests 3%)

bun run build passed. git diff --check passed. No Rust files changed, so Rust fmt, clippy and test gates were not run. cargo clean output:

Removed 8655 files, 7.3GiB total

Performance verification

No valid before/after performance profile was produced. The first locked perf-VM run exited before timing because the staged media runtime referenced Nix paths absent on the Debian VM. Repointing it at the VM's system media tools did not clear the startup failure; the next run also exited with DecoderRejected / NotFound before tracing.

The local fallback started at load average 42.55/44.63/46.53. Its final attempt recorded one average-before trace, then timed out waiting for the first target. The recorded profile had one case and no summary; it is not a baseline. The perf VM was then reserved by tabstorm-1338 at 11:24:41 CEST for 35 minutes, through the four-hour job cutoff. I cancelled the queued probe and did not claim the p95 target. docs/perf/baseline.json remains unchanged.

The hot-path profile still needs one controlled run through perf-run. It must show three paired runs for each of 20 and 500 registered Tags, with pointermove_frame_scripting_ms.p95_ms below 16 ms at 500 Tags.

UX gaps closed

  • Pointer and touch share the same frame scheduler; pointer-up flushes the last point.
  • Valid target feedback and accessible source/target status remain shared with keyboard merge and Undo.
  • The 44 px Tag row target, Escape cancellation, confirmation and durable writer remain in place.
  • Six real-build screenshots cover the Today Tag sidebar and six cover the Tasks Tag sidebar, at 390/820/1440 px in light/dark, using macOS platform emulation. All use real API fixtures.

UX gaps left

  • The current pointer merge end-to-end flow did not complete under shared-host load; it timed out waiting for the DnD clone. Keyboard merge and held-touch merge plus Undo passed in earlier focused flows, but a clean current pointer/keyboard/touch flow pass is still needed.
  • Drag/hover/drop-state screenshots were not captured across all six viewport/theme combinations. The attached screenshots show stable real Tag sidebar rows.
  • The controlled performance profile and p95 result remain outstanding.
  • The full web suite has the 12 timeout failures listed above.

Decisions

  • Use elementFromPoint and a registered-zone Set. A Tag hit is the deepest registered DOM ancestor, so per-zone rectangle caching is unnecessary.
  • Keep all 500 zones registered in the worst-case profile, then move through up to 20 visible targets. This avoids scrolling the active drag path while retaining the 500-zone lookup workload.
  • Keep the shared touch hold at 450 ms and use the existing shared merge writer, confirmation and Undo path.
  • The Tag adapter unit tests now mock the shared DnD primitive; the earlier low-level native gesture assertions were replaced when gesture ownership moved to that primitive. The real-browser flow is the intended coverage for held touch and pointer ownership, but the current pointer flow still needs a clean pass.

Screenshot attachments

Today Tag sidebar: light 390 px · light 820 px · light 1440 px · dark 390 px · dark 820 px · dark 1440 px

Tasks Tag sidebar: light 390 px · light 820 px · light 1440 px · dark 390 px · dark 820 px · dark 1440 px

Current screenshot manifest

For the merge round

  • Run cd apps/web && bun run test -- --maxWorkers=2 to prove the full web suite passes.
  • Run the real-server Tag flow with Mac emulation to prove pointer, keyboard and touch merge plus Undo.
  • Run bench/tag-dnd-1187.mjs under perf-run calternal tagdnd2-1187 25 -- ... to capture the three interleaved before/after Chrome traces at 20 and 500 Tags and verify the 16 ms p95 target.
# Forgejo #1187 — final report **Branch:** `job/tagdnd-1187` **Head:** `74204854893b3e340f5d40967c82b0c67231c2dd` ## Built - Tag pointer and held-touch lookup now coalesces movement through one `requestAnimationFrame` callback. The callback uses `elementFromPoint` and registered-zone ancestor membership, then checks only the hit zone's one-item list. Pointer-up flushes the last queued point. - Target and item state publication is idempotent. A pointer staying over the same Tag does not write reactive state. - The hidden live-row preview is removed from hit testing. Shared `TagPill`, `OverlaySurface`, `ConfirmSheet`, `ItemActionMenu`, `Pill`, `Menu` and `TextInput` remain the visible controls. - The benchmark profile creates real 20- and 500-Tag Instances and records three paired before/after runs, Chrome traces, pointermove dispatch and scripting, frame scripting, long tasks, INP candidates, CPU and RSS. **Expected cost:** register O(N) Tag zones once at drag start. Each pointer frame then walks the DOM ancestor chain and checks one target zone with one item. It does not read every zone's rectangle or publish state while the target is unchanged. ## Files - `apps/web/e2e/tags-1110-1111.mjs` - `apps/web/src/lib/actions/tagDrag.svelte.test.ts` - `apps/web/src/lib/actions/tagDrag.svelte.ts` - `apps/web/src/lib/components/TagPill.svelte.test.ts` - `apps/web/src/lib/components/TagRow.svelte` - `apps/web/src/lib/components/TagRow.svelte.test.ts` - `apps/web/src/lib/components/app-sidebar.svelte` - `apps/web/src/lib/tasks/TaskColumn.svelte` - `apps/web/src/lib/tasks/TasksSidebar.svelte` - `apps/web/src/lib/vendor/svelte-dnd-action/index.d.ts` - `apps/web/src/lib/vendor/svelte-dnd-action/index.mjs` - `bench/tag-dnd-1187.mjs` - `contracts/perf/adoption-1058.json` - `contracts/perf/exceptions.json` - `packages/ui/README.md` - `packages/ui/src/components/OverlaySurface.svelte` - `packages/ui/src/components/TagPill.svelte` - `packages/ui/src/tokens.css` ## Gates `cd apps/web && bun run check`: ```text perf-lint: PASS; 0 violations; 22548 scoped exceptions User browser caches use userStorage; only documented device/public-link exceptions remain. Glass alpha, blur and backdrop-filter roles use packages/ui/src/tokens.css. Text sizes and UI shape values use shared role tokens. Keyboard focus rings use the shared focus tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/tagdnd-1187/apps/web Getting Svelte diagnostics... /home/kayg/Developer/calternal-wt/tagdnd-1187/apps/web/../../packages/ui/src/components/calendar/AttachmentDeck.svelte:1055:3 Warn: Do not use empty rulesets (css) } .attachment-row:focus { ==================================== svelte-check found 0 errors and 1 warning in 1 file ``` `bun run test -- --maxWorkers=2`: ```text Test Files 12 failed | 276 passed (288) Tests 12 failed | 2026 passed (2038) Start at 08:59:51 Duration 2128.13s (transform 31%, environment 27%, import 21%, tests 14%, setup 7%) Environment |component| jsdom was created 115 times · 965.21s total, 31% 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#caching-between-reruns error: script "test" exited with code 1 ``` The 12 failures were 5-second timeouts in unrelated Settings tests under shared-host load. The focused Tag suites passed: ```text Test Files 3 passed (3) Tests 12 passed (12) Start at 09:35:57 Duration 92.79s (transform 78%, import 8%, environment 7%, setup 3%, tests 3%) ``` `bun run build` passed. `git diff --check` passed. No Rust files changed, so Rust fmt, clippy and test gates were not run. `cargo clean` output: ```text Removed 8655 files, 7.3GiB total ``` ## Performance verification No valid before/after performance profile was produced. The first locked perf-VM run exited before timing because the staged media runtime referenced Nix paths absent on the Debian VM. Repointing it at the VM's system media tools did not clear the startup failure; the next run also exited with `DecoderRejected` / `NotFound` before tracing. The local fallback started at load average `42.55/44.63/46.53`. Its final attempt recorded one `average-before` trace, then timed out waiting for the first target. The recorded profile had one case and no summary; it is not a baseline. The perf VM was then reserved by `tabstorm-1338` at 11:24:41 CEST for 35 minutes, through the four-hour job cutoff. I cancelled the queued probe and did not claim the p95 target. `docs/perf/baseline.json` remains unchanged. The hot-path profile still needs one controlled run through `perf-run`. It must show three paired runs for each of 20 and 500 registered Tags, with `pointermove_frame_scripting_ms.p95_ms` below 16 ms at 500 Tags. ## UX gaps closed - Pointer and touch share the same frame scheduler; pointer-up flushes the last point. - Valid target feedback and accessible source/target status remain shared with keyboard merge and Undo. - The 44 px Tag row target, Escape cancellation, confirmation and durable writer remain in place. - Six real-build screenshots cover the Today Tag sidebar and six cover the Tasks Tag sidebar, at 390/820/1440 px in light/dark, using macOS platform emulation. All use real API fixtures. ## UX gaps left - The current pointer merge end-to-end flow did not complete under shared-host load; it timed out waiting for the DnD clone. Keyboard merge and held-touch merge plus Undo passed in earlier focused flows, but a clean current pointer/keyboard/touch flow pass is still needed. - Drag/hover/drop-state screenshots were not captured across all six viewport/theme combinations. The attached screenshots show stable real Tag sidebar rows. - The controlled performance profile and p95 result remain outstanding. - The full web suite has the 12 timeout failures listed above. ## Decisions - Use `elementFromPoint` and a registered-zone Set. A Tag hit is the deepest registered DOM ancestor, so per-zone rectangle caching is unnecessary. - Keep all 500 zones registered in the worst-case profile, then move through up to 20 visible targets. This avoids scrolling the active drag path while retaining the 500-zone lookup workload. - Keep the shared touch hold at 450 ms and use the existing shared merge writer, confirmation and Undo path. - The Tag adapter unit tests now mock the shared DnD primitive; the earlier low-level native gesture assertions were replaced when gesture ownership moved to that primitive. The real-browser flow is the intended coverage for held touch and pointer ownership, but the current pointer flow still needs a clean pass. ## Screenshot attachments **Today Tag sidebar:** [light 390 px](https://git.kayg.org/attachments/4429621f-c839-4d83-b08b-64630c151d37) · [light 820 px](https://git.kayg.org/attachments/85a729c5-14d0-49f5-9092-f70a42cd0a51) · [light 1440 px](https://git.kayg.org/attachments/e56e6fd9-325a-4103-b596-f05cbba455c4) · [dark 390 px](https://git.kayg.org/attachments/129ab44f-8757-4b8d-a89c-1b79bb18ea10) · [dark 820 px](https://git.kayg.org/attachments/b0abab5e-f5e8-481a-ba17-4fdadb57e336) · [dark 1440 px](https://git.kayg.org/attachments/79773e7f-35d0-45b3-8c8d-6855a9fc0117) **Tasks Tag sidebar:** [light 390 px](https://git.kayg.org/attachments/78e67a14-05c8-4520-84a3-e9f18308a975) · [light 820 px](https://git.kayg.org/attachments/2dad5c36-ba03-47c2-85b9-a5101aada392) · [light 1440 px](https://git.kayg.org/attachments/4029b432-4bc1-4583-b3b4-2210e4ea72a7) · [dark 390 px](https://git.kayg.org/attachments/3cd393bf-3a8e-4493-8616-050b3707b75e) · [dark 820 px](https://git.kayg.org/attachments/2ba2bfeb-b7a8-4be3-89bd-1082738f76b4) · [dark 1440 px](https://git.kayg.org/attachments/d8384185-c0c9-4830-a6b-37694f862123) [Current screenshot manifest](https://git.kayg.org/attachments/3300cf1a-7d4f-4230-a4a7-5213041ab4e6) ## For the merge round - Run `cd apps/web && bun run test -- --maxWorkers=2` to prove the full web suite passes. - Run the real-server Tag flow with Mac emulation to prove pointer, keyboard and touch merge plus Undo. - Run `bench/tag-dnd-1187.mjs` under `perf-run calternal tagdnd2-1187 25 -- ...` to capture the three interleaved before/after Chrome traces at 20 and 500 Tags and verify the 16 ms p95 target.
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
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-v0#1187
No description provided.