T-6OI8-ui-extract-anchored-position-and-chords
State: closed/done · Impact: high · Complexity: small
The markdown chrome needs three floating overlays (bubble menu, link popover, hover preview) and
chord hints, and the code for both already exists in the wrong tier: viewport measure/flip/clamp is
inlined in DContextMenu.vue and duplicated in NotesHoverPreview.vue; portable chord parsing
lives in app-frame, which markdown-ui must not depend on. This task lifts both into
@determined/ui and deletes the originals.
| Location | Role today |
|---|---|
packages/ts/ui/app/components/DContextMenu.vue | size, MARGIN, position (flip above when overflowing below, clamp left), maxHeight, two-pass measure() — all inline |
solutions/determined/experiences/notes/app/components/NotesHoverPreview.vue#anchorTo | Second copy: below-or-above by remaining height, clamp to viewport width |
packages/ts/app-frame/app/utils/chords.ts | parseChord, matchChord, chordHint, isEditableTarget; a local DOs type copy because the frame has no dependency edge to ui |
packages/ts/app-frame/tests/chords.test.ts | Unit suite over the three functions |
packages/ts/app-frame/app/composables/useKeybindings.ts | Imports matchChord relatively |
packages/ts/app-frame/app/composables/useContextMenu.ts | Imports chordHint relatively |
packages/ts/app-frame/app/components/ShellPalette.vue | Imports chordHint relatively |
packages/ts/app-frame/app/components/ScopeFocusBand.vue | Imports isEditableTarget relatively |
packages/ts/app-frame/app/composables/useContributions.ts | Doc comment cites the chords module |
packages/ts/app-frame/app/composables/usePaneCommands.ts | Doc comment cites the chords module |
packages/ts/app-frame/app/composables/useDeepLinks.ts | Header comment cites the chords / useKeybindings split |
packages/ts/app-frame/app/composables/useNotifications.ts | Header comment cites the same split |
packages/ts/app-frame/app/plugins/menu-bridge.client.ts | Comment cites the chords rule |
packages/ts/app-frame/app/utils/deepLinks.ts | Header comment mirrors the chords module’s split |
packages/ts/ui/app/utils/keys.ts | Header says it sits here because it cannot import app-frame’s chords module |
packages/ts/ui/app/composables/useDOs.ts#DOs | The DOs type the chords module duplicates |
solutions/determined/packages/shell/nuxt.config.ts | Extends ['@sksizer/app-frame', '@determined/ui']; every app-frame consumer also extends ui (shell, suspenders, system-graph, ontological desktop) |
Proposed
Section titled “Proposed”packages/ts/ui/app/composables/useAnchoredPosition.ts:
interface AnchorRect { top: number; left: number; bottom: number; right: number; width: number; height: number }useAnchoredPosition(anchor: Ref<AnchorRect | null>, el: Ref<HTMLElement | null>, opts?: { margin?: number // default 8 placement?: 'below' | 'above' | 'auto' // default 'auto': below unless it overflows and above fits align?: 'start' | 'center' // horizontal, default 'start'}): { position: ComputedRef<{ top: number; left: number }> maxHeight: string // calc(100dvh - 2*margin) style: ComputedRef<CSSProperties> measure(): Promise<void> // two-pass: measure, let max-height clamp, measure again}A point anchor is a zero-size rect. DContextMenu.vue consumes it with
{ top: y, left: x, bottom: y, right: x, width: 0, height: 0 } and keeps its behaviour and header
rationale (the rationale moves into the composable’s docstring).
chords.ts moves verbatim to packages/ts/ui/app/utils/, importing DOs from the useDOs
composable. Its test moves beside it as chords.test.ts (ui’s beside-the-source convention). The
four app-frame importers drop their relative imports and use the auto-imported names — every
product that extends app-frame also extends @determined/ui, the same route useDOs already takes.
Comments that cite the old home are updated.
Approach
Section titled “Approach”- Write
packages/ts/ui/app/composables/useAnchoredPosition.test.tsunder happy-dom with stubbedwindow.innerWidth/innerHeightandoffsetWidth/offsetHeight: below by default; flips above when overflowing below and there is room; clamps left and right;maxHeightstring; second measure re-reads after the clamp. - Write the composable; rewrite
DContextMenu.vueover it; run its stories. git mvthe chords module and its test into ui; change theDOsimport;bun test packages/ts/ui.- Remove the four relative imports in app-frame; update the six comment sites and the ui
keys.tsheader. bun test packages/ts/app-frame;moon run product-workbench:check(auto-import resolution).
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
packages/ts/ui/app/composables/useAnchoredPosition.ts | new | The composable |
packages/ts/ui/app/composables/useAnchoredPosition.test.ts | new | Flip, clamp, cap, two-pass tests |
packages/ts/ui/app/components/DContextMenu.vue | modify | Consume useAnchoredPosition; delete inline measure/flip/clamp |
packages/ts/ui/app/utils/chords*.ts | new | chords.ts and chords.test.ts moved from app-frame; DOs imported from useDOs |
packages/ts/ui/app/utils/keys.ts | modify | Header no longer cites app-frame’s chords module |
packages/ts/app-frame/app/utils/chords.ts | delete | Moved to ui; importers and comment sites listed below |
packages/ts/app-frame/tests/chords.test.ts | delete | Moved to ui |
packages/ts/app-frame/app/composables/useKeybindings.ts | modify | Drop relative import |
packages/ts/app-frame/app/composables/useContextMenu.ts | modify | Drop relative import |
packages/ts/app-frame/app/components/ShellPalette.vue | modify | Drop relative import |
packages/ts/app-frame/app/components/ScopeFocusBand.vue | modify | Drop relative import |
packages/ts/app-frame/app/composables/useContributions.ts | modify | Comment cites the ui home |
packages/ts/app-frame/app/composables/usePaneCommands.ts | modify | Comment cites the ui home |
packages/ts/app-frame/app/composables/useDeepLinks.ts | modify | Comment cites the ui home |
packages/ts/app-frame/app/composables/useNotifications.ts | modify | Comment cites the ui home |
packages/ts/app-frame/app/plugins/menu-bridge.client.ts | modify | Comment cites the ui home |
packages/ts/app-frame/app/utils/deepLinks.ts | modify | Comment cites the ui home |
packages/ts/ui/README.md | modify | List useAnchoredPosition and the chord utils |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
bun test packages/ts/uipasses, including the anchored-position test and the moved chords test. - AC-2:
packages/ts/app-frame/app/utils/chords.tsdoes not exist andgrep -rn "utils/chords" packages/ts/app-framereturns nothing. - AC-3:
grep -n "innerHeight\|innerWidth\|offsetHeight"overpackages/ts/ui/app/components/DContextMenu.vuereturns nothing; the component positions only through the composable. - AC-4:
bun test packages/ts/app-frameandmoon run product-workbench:checkpass. - AC-5: the
DContextMenuStorybook stories render at a bottom-edge cursor with the menu fully inside the viewport (existing story, visually checked in light and dark). - AC-6: the moved
chords.tscontains no localDOsdeclaration (grep -n "type DOs"over it returns nothing).
Out of scope
Section titled “Out of scope”- Replacing
NotesHoverPreview.vue’sanchorTo; that component is lifted whole in T-RJVA-md-ui-find-outline-hover-lifts, which consumes the composable. - Any change to chord semantics (
matchChordstill refuses editable targets). - Markdown chrome components that consume these (T-8ZHI-md-ui-overlays-bubble-link-table, T-98EU-md-ui-toolbar-insert-frontmatter).
Dependencies
Section titled “Dependencies”- No task dependency.
docs/PRINCIPLES.md: generalizing deletes the original, so the app-frame copies go rather than becoming shims.
Discovery context
Section titled “Discovery context”Member of milestone M-931L; the library-extraction rows of decision D-K3EM-markdown-editor-chrome-and-plugin-environment.