Add Qalma UI Kit documentation and release setup - #24
Conversation
Track the @qalma/kit extraction slice by slice: scoped decisions, phased plan, and a running log, so the work can proceed incrementally without re-litigating scope each time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
`PointerEvent` isn't defined in the jsdom test environment this repo uses (pointer events are simulated via `MouseEvent`), so the `instanceof PointerEvent` check added for cursor-row tracking threw a ReferenceError under vitest. `PointerEvent` extends `MouseEvent` in real browsers, so checking `instanceof MouseEvent` is compatible with both and fixes the crash. Updates the one test whose expected handle transform still reflected the old fixed-offset positioning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
First slice of the UI kit plan (see UI_KIT_PLAN.md): a new publishable Nx lib, libs/ui-kit, following the same secondary-entry-point discipline as @qalma/editor/table and /forms. - Add cn() (clsx + tailwind-merge) and a class-variance-authority convention, replacing the ad-hoc clsx(BASE, VARIANTS[x]) pattern that had no shared helper. - Migrate the two existing ad-hoc primitives (button, progress) out of apps/docs/src/app/ui/ into the lib as QalmaButton/QalmaProgress*, renamed to the qalma prefix used by the rest of the public surface. - Rewire every consumer in apps/docs to import from @qalma/kit instead of the local app-only files, and update the theming doc's example. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Second slice of the UI kit plan: pure, tested building blocks for the floating UI features (drag handle, link popover, mention/slash-command menus, contextual toolbar) that currently each hand-roll their own rect-clamping and dismiss logic. - anchorToRect: placement (top/bottom/left/right) + align (start/center/end, or a numeric cross-axis position to follow a pointer), with horizontal AND vertical clamping to a boundary rect. - DismissibleOverlay: shared outside-click + Escape dismissal. - KeyboardNavigableList: arrow-key navigation with wraparound + Enter to select, for autocomplete-style popovers. Evaluated `@spartan-ng/brain`'s BrnPopover first: it anchors to a real trigger element (`brnPopoverTriggerFor`, extends BrnDialog), not an arbitrary rect, so it doesn't fit a ProseMirror selection or mention range that has no backing DOM element. Hence a small dedicated primitive instead of adopting CDK Overlay wholesale here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Third slice: replace duplicated logic in apps/docs with the shared primitives instead of leaving them unused, per review feedback. - Fix a real anchorToRect bug found while integrating it: when the floating element is taller than the anchor (a 30px button against a 24px single-line block is the common case, not an edge case), the cross-axis clamp produced an arbitrary result instead of centering. - drag-handle-controller.ts: updateFromBlock now uses anchorToRect (placement 'left', align = pointer position). Bit-perfect migration, the existing pixel-exact test passes unchanged. - link-popover.model.ts: createLinkPopoverPlacement now uses anchorToRect (placement 'bottom'), fixing the real bug where it never clamped vertically. - mention.ts / slash-command.ts: wired KeyboardNavigableList for arrow-key/Enter navigation (Escape/Tab/Space stay feature-specific). This forced an API fix on the primitive itself: handleKeydown(event) -> handleKey(key: string), since both features relay keys through a CustomEvent, not a real KeyboardEvent. Deliberately NOT migrated: selection-toolbar-controller.ts (its point-anchor + CSS self-centering approach suits dynamic-width content better than a fixed-size primitive), and the positioning (not keyboard-nav) in mention.ts/slash-command.ts (their flip-above + dynamic maxHeight logic is more advanced than anchorToRect today, migrating would regress it). DismissibleOverlay stays unused: the 4 existing dismiss flows are heterogeneous enough that swapping them in would mean changing behavior, not just deduplicating. Verified live in the browser: drag handle positioning and no-jump on menu open, mention/slash-command keyboard nav and Enter/Escape through the real controller instances. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…oolbar Add a generic `<qalma-toolbar-button command icon label>` element component to `@qalma/kit`: it renders a native `<button>` wired to the editor's `QalmaCommand` directive plus an `<ng-icon>`, so a toolbar becomes a list of declarative buttons instead of the repeated six-line `<button qalmaCommand><ng-icon></button>` block. The host uses `display:contents` so the inner button stays a direct flex item of the surrounding `<qalma-toolbar>` (gap/align preserved). Wire it into `PlaygroundToolbar` — 34 command buttons migrated with the exact shadcn styling kept as the component default (bit-perfect). The image/upload/ link buttons stay custom controls (own click handlers, no `qalmaCommand`). `@qalma/kit` now depends on `@qalma/editor` and `@ng-icons/core` as optional peerDependencies (external, not bundled) — no tree-shaking regression for consumers that only import `cn`/`anchorToRect`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a data-driven toolbar layer to `@qalma/kit`: - `toolbar-commands.ts`: exported `ToolbarCommandItem` fragments (headings/marks/align/lists/table/history + clear-formatting/unset-link) as the reusable source of truth for command/icon/label, plus a `ToolbarTemplateItem` escape hatch (a `TemplateRef` for custom controls). - `QalmaToolbarRegistry` (`<qalma-toolbar-registry>`): renders groups of items separated by dividers, skips empty groups, and delegates each item to `QalmaToolbarButton` or an `ngTemplateOutlet`. Host uses `display:contents` so it stays layout-transparent inside the editor's `<qalma-toolbar>`. - `provideQalmaToolbarIcons()`: registers the Lucide set the fragments need. Rewire `PlaygroundToolbar` to build its groups from the fragments and hand them to the registry, interleaving the color pickers, code-language select and image/link controls as `<ng-template>` items. Table editing ops are composed in only while `isInTable`. The local `provideIcons` shrinks from 40 to the 6 custom-control icons. `@ng-icons/lucide` is added as an optional peerDependency (only the icon provider imports it, so it stays tree-shakeable for consumers that don't). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the block drag handle out of apps/docs and into @qalma/kit so any consumer can reuse it: - `QalmaDragHandleController`: the pointer-tracking / drop-candidate / anchor logic (already `anchorToRect`-based), unchanged beyond the rename. - `qalmaDragHandle` directive: thin adapter that wires the controller to the content surface. - `<qalma-drag-handle>`: the presentational handle + hardcoded block-actions menu (select / move up / move down / duplicate / delete), themed with the shadcn tokens it already used. Wire `DismissibleOverlay` (the Phase-2 primitive that had no consumer yet) into the menu so an outside pointerdown or Escape closes it while the handle itself stays; a click on the handle or menu is treated as inside. Rewire both docs consumers — the playground and examples/notion-doc — onto the kit component, and move the controller spec (10 tests, mounts a real editor with DragHandlePlugin) alongside it. Internal `data-playground-drag-*` markers become `data-qalma-drag-*`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the two caret-anchored suggestion menus out of apps/docs and into @qalma/kit, sharing their common mechanics: - `flipAbovePlacement()` + `SuggestionMenuPlacement`: the flip-above geometry (open below, flip above when cramped, clamp height and left) that both menus duplicated in their `createPlacement` helpers. Parallels `anchorToRect` for the case it doesn't cover. - `QalmaSuggestionMenu<T>`: an abstract base directive with the placement / active-index inputs, activate/pick/dismiss outputs, and the in-menu keyboard handling (Escape, Arrow + focus, Enter/Space, wrap) with optional scroll-into-view. - `QalmaMentionMenu` (avatar + loading) and `QalmaSlashCommandMenu` (icon + shortcut + header/footer, self-provided icons) render only their own items. The mention and slash-command controllers stay in apps/docs (they own the suggestion data and insertion) but now consume the kit option/placement types and `flipAbovePlacement`. The old menu components are removed and all three consumers — the playground, notion-doc and comment-box examples — move onto the kit components. The two `createPlacement` helpers differed by a `gap` on the open-above threshold and floor; the shared primitive unifies them on one consistent formula. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the link preview/edit popover out of apps/docs and into @qalma/kit. Like the drag handle, it is fully generic — no app-specific data — so all three pieces move cleanly: - the model (`LinkPopover`, `createLinkPopoverPlacement` on `anchorToRect`, `findEditorLinkElement`), - `LinkPopoverController` (hover-aware show/preview/edit/save/remove with the 160ms hide delay), - `QalmaLinkPopover`, the presentational preview/edit dialog. The 160ms hover-delay dismiss is kept as-is (this is a hover preview, not a menu, so no `DismissibleOverlay`). Internal `data-link-popover` becomes `data-qalma-link-popover`. All four docs consumers — the playground, comment-box, mail-box and product-review examples — move onto the kit component/controller. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the selection formatting toolbar out of apps/docs and into @qalma/kit, completing the Phase 4 feature extractions. Like the drag handle and link popover it is fully generic, so all three pieces move as-is: - `QalmaContextualToolbar`: the floating bold / italic / inline-code / monospace / link toolbar. - `QalmaSelectionToolbarController`: computes the point-anchor placement. - the `qalmaSelectionToolbar` directive wiring the controller to the surface. Its positioning is deliberately kept as the point-anchor transform with a CSS `translate(-50%, -100%)` self-centering step — the one wrapper that does not use `anchorToRect`, since the toolbar width is dynamic. The playground is the only consumer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migrate the sandbox toolbar off its hand-rolled slate/sky buttons onto the kit so it dogfoods @qalma/kit as a second, independent consumer: - Define the shadcn design tokens (`:root` + `@theme inline`, the docs' warm "ink on paper" palette, light mode only) in the sandbox styles. Crucially no spartan-ng is imported — declaring the tokens is all the kit needs to theme itself, which is the point of the exercise. - Rebuild `sandbox-toolbar` around `QalmaToolbarRegistry` + the command fragments + `provideQalmaToolbarIcons`, keeping the sandbox's own button order and its custom controls (image/upload, highlight & colour swatches, code-block language select, link) as `<ng-template>` items restyled with the shadcn tokens (`TOOLBAR_BUTTON_CLASS` reused for the icon buttons). Also add a `sandbox` entry to .claude/launch.json for previewing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Finish dogfooding the kit in the sandbox: the mention/slash menus and the link popover now come from @qalma/kit instead of hand-rolled copies. - Point the `sandbox-mention` / `sandbox-slash-command` controllers at the kit option/placement types and `flipAbovePlacement`, dropping their duplicated `createPlacement` geometry (identical to the docs formula). - Delete the sandbox menu components and the local link-popover component/controller/model; render `QalmaMentionMenu`, `QalmaSlashCommandMenu` and `QalmaLinkPopover` with the kit `LinkPopoverController` instead. The sandbox link placement gains the kit's `anchorToRect` vertical clamping (its local copy never clamped). - Rewire `sandbox-editor` and repoint the link-popover test onto `QalmaLinkPopover` (same aria-labels, so the assertions are unchanged). The sandbox now runs entirely on the kit chrome, themed only through its own shadcn tokens — no spartan-ng. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a standalone UI Kit documentation section with live component pages, install tabs, theming guidance, and shared docs chrome. Keep the navigation progress indicator local to the docs app, align code copy affordances across docs surfaces, and prepare @qalma/kit release metadata and public guidance.
Run sandbox Playwright tests against a dedicated serve target on port 4201 so an existing docs server on 4200 cannot be reused by mistake. Update the toolbar e2e expectations to match the UI Kit list command labels exposed to consumers.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e9b4614696
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }, | ||
| "release": { | ||
| "projects": ["editor", "skills"], | ||
| "projects": ["editor", "skills", "ui-kit"], |
There was a problem hiding this comment.
Publish @qalma/kit from the tag workflow
Adding ui-kit to the Nx release projects makes tag releases build/version dist/libs/ui-kit, but the release workflow I inspected still verifies only dist/libs/editor dist/libs/skills and publishes only dist/libs/skills dist/libs/editor (.github/workflows/release.yml lines 101 and 114). In the tagged-release path this leaves the new @qalma/kit package unpublished even though the docs and release config advertise it as releasable, so include dist/libs/ui-kit in the unpublished check and publish loop before relying on this setup.
Useful? React with 👍 / 👎.
Summary
/kitdocumentation section for the Qalma UI Kit, with per-component pages, live examples, install tabs, theming guidance, icons, and primitives guidance@qalma/kitrelease metadata and updates public agent guidance for consumersValidation
prettier --checkon touched docs and UI Kit filesgit diff --checknx lint docs --skip-nx-cachenx lint ui-kit --skip-nx-cachenx test ui-kit --skip-nx-cache -- --runnx test docs --skip-nx-cache -- --runnx test sandbox --skip-nx-cache -- --runnx build ui-kit --skip-nx-cachenx build docs --skip-nx-cachenx build sandbox --skip-nx-cache(passes with the existing initial bundle budget warning: +137.42 kB)node tools/guard-protected-test-changes.mjs/kit,/kit/theming, header nav order, copy-button de-duplication, and inline-code stylingNotes
approved-test-changelabel depending on repository policy.