Skip to content

Add Qalma UI Kit documentation and release setup - #24

Merged
cdskill merged 16 commits into
mainfrom
feature/ui-kit
Jul 7, 2026
Merged

Add Qalma UI Kit documentation and release setup#24
cdskill merged 16 commits into
mainfrom
feature/ui-kit

Conversation

@cdskill

@cdskill cdskill commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • adds a standalone /kit documentation section for the Qalma UI Kit, with per-component pages, live examples, install tabs, theming guidance, icons, and primitives guidance
  • aligns docs code rendering and copy-button behavior across Markdown docs, examples, and UI Kit pages
  • keeps the navigation progress indicator local to the docs app and removes it from the public UI Kit package
  • prepares @qalma/kit release metadata and updates public agent guidance for consumers

Validation

  • prettier --check on touched docs and UI Kit files
  • git diff --check
  • nx lint docs --skip-nx-cache
  • nx lint ui-kit --skip-nx-cache
  • nx test ui-kit --skip-nx-cache -- --run
  • nx test docs --skip-nx-cache -- --run
  • nx test sandbox --skip-nx-cache -- --run
  • nx build ui-kit --skip-nx-cache
  • nx build docs --skip-nx-cache
  • nx build sandbox --skip-nx-cache (passes with the existing initial bundle budget warning: +137.42 kB)
  • node tools/guard-protected-test-changes.mjs
  • Playwright smoke checks for /kit, /kit/theming, header nav order, copy-button de-duplication, and inline-code styling

Notes

  • Protected test/config files are touched, so CI may require the approved-test-change label depending on repository policy.

cdskill and others added 16 commits July 4, 2026 00:19
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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread nx.json
},
"release": {
"projects": ["editor", "skills"],
"projects": ["editor", "skills", "ui-kit"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

@cdskill
cdskill merged commit ce28949 into main Jul 7, 2026
1 of 2 checks passed
@cdskill
cdskill deleted the feature/ui-kit branch July 7, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant