feat(dtpr-ai): taxonomy catalog + element/category pages + Cmd-K + locale#276
feat(dtpr-ai): taxonomy catalog + element/category pages + Cmd-K + locale#276
Conversation
Add /taxonomy page that browses every element of the active AI schema version. Resolves the latest canonical version client-side via /schemas (the API does not honor an @latest path alias today), normalizes the REST response's singular category_id into the plural shape the grouping helper expects, and renders DtprCategorySection + DtprElementGrid + DtprElement using deriveElementDisplay with composed icon URLs. Wires @dtpr/ui as a workspace dependency on dtpr-ai.
Add a sticky header with a UInput that filters the catalog case-insensitively against locale-resolved title and description, hides categories whose elements all drop out, and renders an empty state when the query matches nothing.
Add per-row and per-category UButton affordances that copy the fully-qualified deep-link to the clipboard and emit a useToast() notification. Read window.location.hash on mount and on hashchange to drive a temporary outline+tint highlight on the targeted row, mirroring the legacy taxonomy's deep-link UX without modifying any @dtpr/ui internals.
Render a UNavigationMenu sidebar with one entry per visible category (label, badge=filtered count, anchor href). A passive scroll listener updates the active state to the topmost section above a 100px offset. On viewports below lg, the sidebar collapses behind a hamburger toggle in the sticky header with a backdrop overlay.
Read the active schema version from ?v=<canonical-id>, falling back to the most recent registered version when the param is absent. When ?v= names a version not in the schemas index, render a UAlert and fall back to latest. The USelectMenu hides itself when only one version is registered today and surfaces automatically once additional schemas ship.
Add a content stub at content/0.taxonomy.md whose 'navigation' frontmatter publishes a sidebar entry for the route. The static app/pages/taxonomy.vue page outranks the catch-all so visitors clicking the nav land on the live catalog rather than the stub body.
Apply safe_auto fixes from ce-code-review (run 20260427-162447-da890dd9): - Type ElementApi as Omit<Element, 'category_id'> so the singular field is actually relaxed (the prior Omit<..., 'category_ids'> was a no-op since Element has no category_ids field) [kieran-1, corr-3] - Preserve route.hash when switching schema version so deep links survive selector changes [corr-2] - Add 8s timeout to /schemas, /categories, /elements fetches so a slow upstream cannot exhaust the Cloudflare Workers SSR wall-clock budget [rel-002] - Track scrollToTarget setTimeout in a ref, clearTimeout on re-entry and on unmount, so rapid clicks and route changes don't leak handlers [julik-2] - Throttle handleScroll with requestAnimationFrame and read offsetTop at most once per frame instead of on every scroll event [julik-3, rel-007] - Suppress scroll-spy briefly during programmatic smooth scroll so the sidebar highlight no longer flickers through every category between source and destination [julik-1] - Auto-close the mobile sidebar after a category is selected so the overlay doesn't cover the destination on touch viewports [julik-6] - Move history.replaceState + targetId update inside the clipboard success branch so a failed copy no longer rewrites the URL the toast just told the user wasn't copied [julik-5, rel-005]
Adds the origin brainstorm and the two plan documents: - 2026-04-27-001: prior catalog plan (already shipped) - 2026-05-05-001: this feature — element pages, category pages, Cmd-K search, and a `?locale=` URL convention Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single source of truth for ?v= and ?locale= consumed by the catalog, both new routes, and the synthetic Cmd-K injection. Lifts the version computed-chain from taxonomy.vue and adds locale-aware setters that push to the router via replace. Default locale is en; default version resolves to ai@latest. Scoped strictly to DTPR-derived surfaces — docus prose stays English-only at the site shell. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… overlays (U2–U5) - /taxonomy/elements/<id>: hero via DtprElementDetail, an interactive Playground (Variables form, Context chip row, Default/Dark style toggle) backed by deriveElementDisplay, and an optional editorial body when an authored md exists at content/taxonomy/elements/<id>.md. - /taxonomy/categories/<id>: title, description, read-only context palette swatches, elements list linking to element pages, and an optional editorial body at content/taxonomy/categories/<id>.md. - DtprPageHeader: shared sticky-header strip owning the version + locale selectors, used across catalog and detail pages. - Editorial overlay convention documented in 0.taxonomy.md; required frontmatter (navigation: false, title:) keeps overlays out of the docus left-rail nav while letting Cmd-K index them automatically. Both pages handle 404 and "not in this version" inline rather than crashing hydration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…page link (U6) Migrates the catalog from its local version computed-chain to the shared useDtprState composable, adds locale awareness end-to-end (?locale= query, &locales=<active>,en API params, locale-aware deriveElementDisplay), and consumes DtprPageHeader so the catalog, element pages, and category pages all share one header strip. Adds per-row "Open page" affordances to every element row and each category section header. The existing copy-link button and the hash-scroll behavior are preserved; the link button is purely additive. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…(U7) Local override of docus's app.vue mirrors the upstream verbatim plus a single bound prop — :groups="dtprGroups" on <LazyUContentSearch>. useDtprSearchOverlay runs a single client-only useAsyncData keyed on the surface's active version + locale, builds two synthetic groups (Elements and Categories), and dedupes against authored editorial md so real markdown wins on collisions. Cmd-K results scope to whatever surface the visitor is currently on: opening the modal on /taxonomy?v=ai@2026-04-16-beta surfaces beta entries; opening it elsewhere uses ai@latest. The boot fetch never blocks initial paint (server: false). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With both `pages/taxonomy.vue` and `pages/taxonomy/elements/[id].vue`, Nuxt's file-based router treats them as parent + nested child. The parent has no `<NuxtPage>` slot, so visiting `/taxonomy/elements/<id>` silently rendered the catalog instead of the element page. Moving the catalog into `taxonomy/index.vue` makes the three routes siblings (`/taxonomy`, `/taxonomy/elements/:id`, `/taxonomy/categories/:id`) and the deep links resolve correctly. Also swaps `v-show` for `v-if` on the search-clear button to silence a Vue warn about runtime directives on a component with a fragment root (UButton → ULink → NuxtLink). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, right-align locale - Shadow docus's AppHeaderCenter locally to add a "Taxonomy" button next to the Cmd-K search trigger. Docus 5.10 has no first-class header.links config, so component shadowing is the standard extension point. - Set `navigation: false` on content/0.taxonomy.md so the entry no longer appears in the docus left-rail sidebar. - Drop the in-page "Search elements…" filter from /taxonomy. Cmd-K is now the unified search surface; the in-page input duplicated effort and competed visually with the new taxonomy top-nav link. Removes searchQuery, filteredDecorated/Grouped/SortedCategories, hasResults, clearSearch, and the no-results empty state — sidebar scroll-spy reads from the unfiltered sortedCategories now. - Locale select right-aligns via `margin-left: auto` and the popup widens to `min-w-[12rem]` so language names like "Português" don't wrap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gnment - Replace the AppHeaderCenter shadow with a shadowed AppHeaderCTA. AppHeaderCTA is docus's canonical empty-by-default extension point on the right side of the header, rendering left of UColorModeButton and the GitHub link — exactly where a "Taxonomy" nav link belongs. - AppHeaderCenter falls back to docus's default (search trigger only), so Cmd-K stays the unified search affordance with no competing link. - Make the DtprPageHeader heading flex-grow so version + locale + actions get pushed to the right edge of the row. The earlier `margin-left: auto` on the locale select alone wasn't enough — the heading was non-flex, leaving the right cluster anchored next to it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
dtpr-ai | a383f4d | May 05 2026, 07:58 PM |
Greptile SummaryThis PR builds out the DTPR-AI documentation site's schema-browsing surface: a
Confidence Score: 5/5The PR adds a large but self-contained documentation surface; all new code paths are read-only API fetches and UI rendering with no mutations to shared state outside the router. The core concerns raised in earlier review rounds — stale payload on SPA navigation, raw query param propagation, limit=200 truncation, and the context chip dark-mode override — have each been addressed in this revision. The element page now uses a dynamic useAsyncData key keyed on element ID; queryString in all three pages is derived from validated activeVersion/activeLocale rather than raw route params; truncation warnings are in place in both taxonomy/index.vue and useDtprSearchOverlay; and DtprPlayground uses effectiveContextId/chipsLocked to correctly reflect the dark-mode override in chip visual state. No new correctness gaps were found across the composables, pages, or components. No files require special attention. The categories/[id].vue elements fetch still omits a meta truncation check (unlike the catalog and Cmd-K composable), but that was already noted in a prior review thread.
|
| Filename | Overview |
|---|---|
| dtpr-ai/app/composables/useDtprState.ts | New composable: centralises version/locale resolution from route query params, validates against a fetched schemas list, exposes writable computeds that call router.replace. Clean design, correct deduplication key for useFetch. |
| dtpr-ai/app/composables/useDtprSearchOverlay.ts | New composable: synthesises Cmd-K groups from DTPR API, correctly guards against truncation with a console warning when meta.total exceeds ELEMENTS_PAGE_LIMIT, and deduplicates against authored md paths. |
| dtpr-ai/app/pages/taxonomy/index.vue | New catalog page: scroll-spy sidebar, hash anchor copy, queryString built from validated state. Truncation alert mirrors the composable. Unused route variable declared but never read — cosmetic dead code. |
| dtpr-ai/app/pages/taxonomy/elements/[id].vue | New element detail page: dynamic useAsyncData key eliminates stale payload on SPA navigation; queryString derived from validated activeVersion/activeLocale; notFound detection via elementError. |
| dtpr-ai/app/pages/taxonomy/categories/[id].vue | New category detail page: static key for all-categories fetch is intentional (data covers all categories, client-side find handles filtering); dynamic key for per-category elements fetch; elements response omits meta/truncation check unlike other consumers. |
| dtpr-ai/app/components/DtprPlayground.vue | New playground component: effectiveContextId correctly mirrors chip visual state to the dark-mode override; variableValues reset on element.id change handles key diffing correctly. |
| dtpr-ai/app/app.vue | Shadowed docus app.vue: single intentional divergence (dtprGroups prop on LazyUContentSearch), well-documented sync requirement for upstream upgrades. |
| dtpr-ai/app/components/DtprPageHeader.vue | New shared sticky header component: version/locale selectors emitted as update events, version-missing alert, slot-based composition for page-specific headings and actions. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
AppVue["app.vue (shadowed)"]
useDtprSearchOverlay["useDtprSearchOverlay\n(Cmd-K groups)"]
useDtprState["useDtprState\n(?v= / ?locale= resolver)"]
DTPR_API["DTPR API\n/schemas/{version}/..."]
AppVue -->|"passes files ref"| useDtprSearchOverlay
useDtprSearchOverlay -->|"calls"| useDtprState
useDtprSearchOverlay -->|"fetches elements + categories\n(server:false, watches activeVersion/Locale)"| DTPR_API
TaxonomyIndex["/taxonomy\n(catalog + scroll-spy)"]
ElementPage["/taxonomy/elements/[id]\n(hero + DtprPlayground)"]
CategoryPage["/taxonomy/categories/[id]\n(palette + elements list)"]
TaxonomyIndex -->|"calls"| useDtprState
ElementPage -->|"calls"| useDtprState
CategoryPage -->|"calls"| useDtprState
TaxonomyIndex -->|"fetches categories + elements\n(dynamic key, limit=200)"| DTPR_API
ElementPage -->|"fetches element + categories\n(key = id, watches id+version+locale)"| DTPR_API
CategoryPage -->|"fetches all categories\n(static key)\nfetches elements by category\n(dynamic key, limit=200)"| DTPR_API
useDtprState -->|"reads ?v= ?locale=\nwrites via router.replace"| RouteQuery["Route query params"]
DtprPageHeader["DtprPageHeader\n(sticky version/locale selectors)"]
TaxonomyIndex --> DtprPageHeader
ElementPage --> DtprPageHeader
CategoryPage --> DtprPageHeader
DtprPlayground["DtprPlayground\n(variables form + context chips)"]
ElementPage --> DtprPlayground
Reviews (2): Last reviewed commit: "fix(dtpr-ai): address greptile review (s..." | Re-trigger Greptile
… scroll - Remove the mobile hamburger toggle, slide-in drawer, and scrim. The toggle was leaking out next to the locale select at responsive widths and the in-page sidebar isn't load-bearing enough on mobile to justify the drawer pattern. Below 1024px the sidebar simply hides — categories are still browseable by scrolling. - Tighten the catalog sidebar's sticky offset so it locks the moment the page begins scrolling, instead of inching upward by ~0.5rem before catching its sticky position. Layout padding-top reduced from 2rem to 1.5rem so the sidebar's natural position equals --ui-header-height + 6rem. - Bump scroll-margin-top on .taxonomy-category and .taxonomy-element-row from 6rem to `--ui-header-height + 5.5rem` so anchor jumps clear both the docus header and the sticky DtprPageHeader instead of landing the title behind them. Scroll-spy threshold raised to 140px so the active highlight tracks the new effective top. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…esolve
Cloudflare Workers Builds runs a fresh `pnpm install --frozen-lockfile`
and then `pnpm run build` — it never builds workspace dependencies
ahead of time. Vite/Rollup can't resolve `@dtpr/ui/core` because the
package's exports map points at `./dist/core/index.js`, and dist/ does
not exist on the build runner.
Local builds masked this: my checkout has a stale dist/ from prior
@dtpr/ui builds. Repro: `rm -rf packages/ui/dist && pnpm --filter
./dtpr-ai build`.
Fix: dtpr-ai's `build` script now runs `pnpm --filter @dtpr/ui build`
first, then nuxt build. Same change to `dev` so a clean clone can
pnpm dev without first hand-building the workspace package.
Also drop the `.gitkeep` files in content/taxonomy/{elements,categories}
— @nuxt/content emits parser-failed warnings on them, and the
directories aren't load-bearing (queryCollection returns null cleanly
when no overlay md exists, and authors create the dir when they drop
the first file).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y rollup
Previous fix built @dtpr/ui first but Cloudflare's build still failed
on `Rollup failed to resolve import "@dtpr/api/validator" from
packages/ui/dist/core/index.js`.
Cause: @dtpr/ui's core/index.js imports `validateInstance` from
@dtpr/api/validator at runtime, and @dtpr/api's exports map points at
`./dist/{schema,validator}/index.js`. Those bundles come from the
`build:schema` tsup pass — not from `wrangler deploy --dry-run`, which
is @dtpr/api's default `build` script and produces only the worker
bundle.
Fix: chain `pnpm --filter @dtpr/api build:schema` before the @dtpr/ui
build. Verified with `rm -rf packages/ui/dist api/dist && pnpm --filter
./dtpr-ai build` from a fully clean state — succeeds without TS2307
warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ion, dark chip)
- Stale element / category data on SPA navigation: useAsyncData keys
now include the route id (`dtpr-element-detail-${elementId}` and
`dtpr-category-detail-elements-${categoryId}`) so Nuxt drops the
prior payload immediately on navigation instead of holding it
through the watch-triggered refetch.
- Forwarded query strings on /taxonomy, /taxonomy/elements/<id>, and
/taxonomy/categories/<id> now derive from useDtprState's validated
activeVersion / activeLocale (gated on requestedVersion + !versionMissing
for `?v=`, gated on locale !== 'en' for `?locale=`), so an invalid
`?locale=xyz` no longer propagates through every link the visitor
clicks afterward.
- Catalog and Cmd-K boot fetch use a named ELEMENTS_PAGE_LIMIT (200,
the API's documented cap) and now check meta.total: the catalog
surfaces an inline "Catalog truncated" UAlert when total > limit,
and useDtprSearchOverlay logs a console.warn with the count so the
truncation can't hide if a future schema outgrows the single page.
- Dark-mode interaction in DtprPlayground: a previously-selected
context chip no longer renders in its solid/active visual state
while dark mode silently overrides it. New `effectiveContextId`
computed returns null while styleMode is 'dark', driving chip
variant + style + aria-pressed; chips are also `:disabled` so
clicks can't accumulate state that won't take effect. The
underlying `contextId` is preserved so switching back to default
restores the visitor's selection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@greptile |
Summary
Builds out the DTPR-AI documentation site's schema-browsing surface from scratch. Two plans land together — the catalog (
/taxonomy) and the per-element / per-category pages plus unified Cmd-K search and a?locale=URL convention.What ships
/taxonomycatalog: every element grouped by category, sticky category sidebar with scroll-spy, copy-link anchors, schema version selector (?v=), per-row + per-category "Open page" affordances./taxonomy/elements/<id>: hero via<DtprElementDetail>with an interactive Playground (Variables form, Context chip row, Default/Dark style toggle) backed byderiveElementDisplay./taxonomy/categories/<id>: title, description, read-only context palette, elements list linking to element pages.content/taxonomy/{elements,categories}/<id>.mdto add authored narrative; Cmd-K indexes it automatically; real markdown wins on collisions with the synthetic group.app.vueinjects two synthetic groups (Elements + Categories) into<UContentSearch>, scoped to the active version + locale.useDtprStatecomposable wires?v=and?locale=consistently across catalog, element pages, category pages, and Cmd-K. DTPR-derived surfaces only — docus prose stays English-only.AppHeaderCTA(left of the dark-mode toggle).Plans
docs/plans/2026-04-27-001-feat-dtpr-ai-taxonomy-view-plan.md— catalog (U1–U7).docs/plans/2026-05-05-001-feat-dtpr-taxonomy-pages-and-search-plan.md— element/category pages, Cmd-K, locale (U1–U7).Test plan
/taxonomyrenders all categories grouped, sidebar scroll-spy works, copy-link button copies#element-<id>URL./taxonomy/elements/<id>renders hero + Playground; typing in a variable input live-updates the rendered title/description; clicking a context chip recolors the icon; Default/Dark toggle swaps the icon variant./taxonomy/categories/<id>renders description, context palette swatches, and the elements list; clicking an element row navigates with?v=and?locale=carried forward.enper field).?v=ai@2026-04-16-betadeep links resolve correctly; unknown versions show the inline alert.content/taxonomy/elements/<known_id>.md— body renders below the playground; Cmd-K dedupes the synthetic entry.🤖 Generated with Claude Code