feat(DateTimePicker): a date-and-time picker with presets - #578
Merged
Merged
Conversation
Groundwork for rebuilding `B24DateTimePicker` on current `main`. PR #55 carried the same component but branched before `main` was re-rooted on 2026-07-10, so it shares no ancestor and cannot be merged or rebased; it is closed, and its design is the specification for this rebuild. Generated with `bitrix24-ui make component DateTimePicker`, which wrote the component, theme, spec, docs page and the nuxt playground page, and registered the entry in `src/runtime/types/index.ts` and `src/theme/index.ts`. Two things the scaffolder did not do, both recorded rather than worked around: - The `ThemeDefaults` entry was skipped silently. `cli/utils.mjs:24` looks for the interface in `src/runtime/composables/useComponentProps.ts` and returns early when it does not match, but `ThemeDefaults` now lives in `src/runtime/types/theme.ts` with 157 entries. So every component scaffolded since that move has quietly missed its entry, and `.github/contributing/component-structure.md:476` still tells the reader the CLI inserts it. Added by hand here; the CLI and the doc need their own change. - The generated component extends `DateTimePickerRootProps` from `reka-ui`, which does not exist — the template assumes a primitive wrapper. Replaced with a minimal valid stub so the branch builds; the real component follows. `dev:prepare`, `lint` and `typecheck` are green on this commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
Written against today's `main` rather than ported from the branch behind #55, which shares no ancestor with it. The design is that PR's: a calendar that hands over to an hour/minute grid, a preset column beside it, `dateOnly` to drop the time step, a `B24Drawer` on small screens and a `B24Popover` elsewhere, and the `default` / `presets` / `preset` / `time-header` slots. Three things are different because the tree is. **No `as unknown as DateValue` anywhere.** The old branch carried those casts throughout, and #56 was filed to clean them up. They were not needed: `ref()` deep-unwraps, which widens the `DateValue` union of class instances into a structural type that no longer matches, and `shallowRef` does not. Date values are immutable, so nothing is lost. That one change took the file from six type errors to one. The last of them is `B24Calendar` typing its emit for every mode it supports — the handler takes the wide type and narrows, rather than casting. **The preset column caps its height with `--max-height-popup-menu`**, the token the menus use, instead of a literal `20rem`. `.sync/PORTING.md` records that those caps are tokens; the side effect is that `test/utils/theme-css-compiles.spec.ts` now covers this class automatically, since it collects exactly that shape. **One fewer string to translate.** The old design had a `pickTime` label for the footer when no time was chosen. The footer shows a time either way, so it falls back to `00:00` and the key is gone — 20 locale files lighter. Every utility the theme uses was compiled through the installed engine before being written down, and every `--ui-*` / `--b24ui-*` variable it references was checked to still exist. The locale strings come from the branch behind #55. Thirteen of them are machine-translated and still want a native reader — that is #60, which stays open and applies here unchanged. Icons are direct imports, not dictionary roles: the dictionary has no calendar or clock key, and `test/utils/icon-claims.spec.ts` forbids documenting one that is not reachable. The JSDoc names the components instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
Eleven render cases and six behaviour tests. The behaviour half is the point: the minute grid is asserted at the default step, at a divisor of 60, above the cap, below the floor and at `NaN`; the selected hour and minute are asserted to be the *only* cells marked pressed; a factory preset is asserted to resolve exactly once per render; and picking a preset is asserted to keep the time in the normal case and to drop it under `dateOnly`. Writing them turned up three defects, all in code written earlier today. **The component emitted `update:open` but had no `open` prop**, so `v-model:open` could not work — a consumer could hear the picker open and never tell it to. `open` and `defaultOpen` are now props, which is also what lets the spec assert the popover content rather than reach into the instance. **Two accessibility defects, both found by `axe` rather than by reading.** The trigger was a `div` that the popover decorates with `aria-haspopup` and `aria-expanded`; `aria-allowed-attr` rejects those on an element with no role. Adding `role="button"` moved the failure rather than fixing it — the wrapper contained a real `<input>`, and `nested-interactive` rejects a control inside a control. The wrapper is gone: `B24Input` forwards fall-through attributes onto its `<input>`, so the readonly input *is* the trigger and the popover's ARIA lands on a real control. The `triggerInput` theme slot went with it rather than staying as a promise nothing keeps. That last one is inherited from the design in #55, where the same wrapper stood. It shipped no tests for the trigger, so nothing said. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
A `### Usage` walkthrough plus sections for `date-only`, presets, `minute-step`, a custom trigger and use inside a `B24FormField`. The examples are real components under `examples/`, so they are typechecked with the docs project. The scaffolded front matter linked the page at Nuxt UI and reka-ui. Neither has this component — `reka-ui.com/docs/components/date-time-picker` answers 404 — so both links are gone rather than left to rot. The page follows the shape of the other b24ui-only components: GitHub and Demo only. Registration the scaffolder does not do, and that two guards in this repository caught rather than letting through: - `docs-component-registries.spec.ts` wants the slug in `pages` in `docs/nuxt.config.ts`; - `skill-manifest.spec.ts` wants the component in the skill index. It went into the Form section beside `B24InputDate` and `B24InputTime` — my first attempt put it under Layout, because that table is grouped by purpose rather than sorted. Both playground pages carry a colour × size matrix with `Date only` and `Hide presets` switches, and are byte-identical to each other. The slug is registered in both `useNavigation.ts` files. Gate: `lint`, `typecheck`, `test` (353 files, 8031 passed, 6 skipped), and `docs:generate` with the CI environment — 1474 routes, no prerender errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
The comment claimed the deep-unwrapping problem is "what forces `as unknown as DateValue` casts elsewhere (#56)". Neither half held: no such cast exists anywhere in `src/` today, and #56 was closed as not planned when PR #55 was abandoned, so the reference points at a decision not to act. Replaced with the measurement: swapping `shallowRef` for `ref` and running `pnpm typecheck` yields seven TS2322/TS2345 errors in this file, all of the same widened-union shape. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
… found
The end-of-week preset resolved through `startOfWeek(…).add({ days: 4 })`,
which reads as Friday only where the week starts on Monday. Measured: it
landed on Thursday under `en` — this repo's own default — and on Wednesday
under `ar`, where it could resolve to the current day and do nothing. The
wrong day was already sitting in the committed snapshot. Anchoring the
weekday index to `en-US` makes Friday 5 for every locale.
Seven locales then said "weekend" rather than "end of week", which is a
different day from the one the preset picks: ja, sc, tc, th, id, ms, la.
Thirteen locales remain the author's approximations and are not claimed
as reviewed.
`open` was not a controlled prop. It was mirrored into a ref that every
internal handler wrote to, so picking a date closed the picker even while
the parent held the prop at `true`. It is now read through the prop, and
the component asks to close by emitting.
A preset's `value` factory is consumer code called during render, and an
exception escaped the computed the template reads: under SSR it rejected
the page, on the client it replaced the component and its working calendar
with an empty node. One bad preset now drops out of the list.
`locale` is a free string feeding four `Intl.DateTimeFormat` constructions,
one of them on the always-visible trigger, so a malformed tag threw before
the picker could be opened. It falls back to `en`.
The spec did not pin the clock, unlike Calendar, InputDate and InputTime,
so the preset hints wrote today's date into the snapshots six times and
would have gone red the next morning.
Nine tests added: accessibility on the time step, `change`, `update:open`,
the controlled `open`, the resolved weekday, the throwing factory, the
rejected locale, a time step opened without a value, and the `defaultValue`
/ `defaultOpen` seeds. Each was checked against a mutation of the code it
covers.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
The transformer simplifies the `b24ui` slot-prop type for the API tables. Its pattern was unbounded in two directions, and both were doing damage. It ran over the whole SFC, so in `<script setup>` it rewrote the same token where it is an object key in an expression. The result does not parse, and `vue-component-meta` answers with empty Props, Slots and Emits for the entire component — no error, no warning, just a blank API section on the page. `B24DateTimePicker` was the only component with zero props of 135 extracted, which is how this surfaced; `B24Theme` and `B24DashboardSearchButton` were being rewritten the same way. The matched run could also cross a brace, so wherever the type was followed by a function signature it swallowed the next slot prop with it — `B24EditorDragHandle`'s `onClick` was being eaten. Restricting it to the plain `<script>` block, where slot interfaces are declared, and forbidding the run from crossing a brace keeps 119 of the 125 sites the old pattern hit across `src/runtime/components` and drops exactly the six it was corrupting. Verified by rebuilding the documentation: the four affected components now extract 21, 2, 34 and 40 props, and the unaffected ones are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
Four visual defects, none of which any test or `axe` could see, because the markup was correct throughout — only the styling never arrived. The colour variant wrote `style-filled` to a `root` slot. That slot was never rendered, and the popover and the drawer both teleport their content out of the component, so it could not have reached the grid either way. Measured in a browser: `--b24ui-background` resolved to an empty string inside the popover, which left the selected hour and minute on `rgba(0, 0, 0, 0)` and the active preset on the default grey border. It is now `#0075ff` on a white glyph, and the variant sits on `content`, which is the element both wrappers actually mount. The dead `root` slot is gone; keeping it invites the same mistake. There was no rule between the hours and the minutes, and the columns drifted apart on a `min-w` instead. The rule now lives on the column itself, with `first:` keeping it off the left edge. The rule under the header, by contrast, should not have been there at all: sitting inside the padding it rendered as a short line floating clear of both edges. Removed — the columns below carry the separation. The control that returns from the time step to the calendar showed a clock. It goes back, so it is a `chevronLeft` now, with `backIcon` to override it and a slot of its own for the hit area. The regression test asserts the colour class is on the teleported content, and goes red when the variant is moved back to `root`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
The time step's header showed only the date, so the value being edited was the one thing it did not say. It now carries the formatted value, time included, which is what the picker this copies puts there. The control that opens the time step was laid out in a flex column and stretched to the full width of the calendar, turning the whole bottom strip into one click target: 252px of 276. `self-start` makes it the width of what it contains, 75px. The grid is always 00-23, but the trigger followed the locale, so under `en` it read `2:30 PM` beside a cell marked 14 and nothing exposed a way to change it. `hour12` now settles it, with a switch in both playgrounds and a section in the documentation. Omitted, it still follows the locale. Edge padding was 8px against the 16px of the design being followed, and the preset column — which sets the height of the whole popover — ran 47px past the bottom of the time grid, leaving a dead band under the minutes. Tighter cards and a wider frame bring that to 27px at 16px edges. The colour of the selected hour and minute was raised as a fourth item. It was already the calendar's: both measure `rgb(0, 117, 255)` in the browser, on the date step and on the time step. Left alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
`DateValue` builds a `CalendarDate` from `[year, month, day]`, so a component that picks a date *and* a time had no way to show a value in a `::component-code` block at all — passing a timestamp fails the page with a bare 500 and no indication of why. `DateTimeValue` takes `[year, month, day, hour, minute]` and builds a `CalendarDateTime`, mirroring the entry beside it. It has to be added twice. The cast table exists in two copies, one in `app/components/content/ComponentCode.vue` for the rendered page and one in `server/utils/transformMDC.ts` for the raw markdown, and a cast present in only one of them fails the other with the same unexplained 500 — which is how this was found. Both copies now say so in a comment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
The two steps are the same control over different units and were styled independently, so they disagreed on every state that matters. The current hour and minute were a grey background pill, where the calendar marks the current date with accent text and a heavier weight. Hover used `--b24ui-background-hover` and applied to the selected cell as well, where the calendar uses `--b24ui-background` and excludes anything already selected. Focus drew an outline against the calendar's ring. The header was sized by a raw token instead of `text-legend`, which is what the calendar's own heading uses. `timeCell` is now `cellTrigger` variant for variant, and the markers are rendered only when true so both themes can use the same bare `data-selected` variant. `data-now` keeps the bracket form: it is not one of Tailwind's built-in boolean data variants, and written bare it compiles to nothing — which is how it first went out with no effect at all. The time step also gets a 252px floor of its own, rather than inheriting a `min-w` from the shared container that the calendar step sized. Measured in a browser: the current cell is now `rgb(0, 117, 255)` on no background at weight 600, hover is `rgb(0, 117, 255)` on white, and the grid is 252px. The rendered matrix only ever reached the calendar step, so none of this was under a snapshot. The time step now has one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
…the cascade Five things, four of them found by looking at the rendered component rather than at the code. Hovering the current hour made the digit vanish. The current-cell rule outranks the hover rule in Tailwind's variant order, so accent text was being painted on an accent background: measured at `rgb(0, 117, 255)` on `rgb(0, 117, 255)`. `not-hover` on the current-cell rule settles it; the hovered cell now measures white on accent. The grid was sized only by `text-label`, which is 16px, while the calendar cell beside it is 14px — the two halves of one picker set in different type. The size variants now carry the calendar's own scale for the cell, the column titles and the header, step for step. Cells were 28px squares. In the drawer, where this is hit with a thumb, that is well under the 44px a touch target needs, and the component is unusable on a phone. The base is now 44px and the compact desktop cell arrives at `sm`; the size variants only ever narrow the `sm:` half, so no setting of `size` can shrink the target. The back control, the preset cards and the footer follow. `color` and `size` stopped at the component: the inner calendar kept its own defaults and stayed blue while the presets and the grid went green. Both now cascade, bound after the spread so `v-bind` cannot overwrite them with an `undefined` key, and overridable through `calendar`. The explanatory comments in the template were rendered into the DOM — 22 nodes in every snapshot, a thing no other component in the kit does. Moved onto the props they describe, where the documentation picks them up too. The snapshots lose 140 lines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
The tests read source, docs and generated files as text and match on literal
`\n` boundaries — front matter fences, `export default {` blocks, the
byte-for-byte `skills/index.json` comparison. With `core.autocrlf=true`, which
is the Windows default, a checkout rewrites the tree to CRLF and those checks
go red against files that are perfectly fine in the repository. Normalise the
whole tree to LF instead of the `skills/**` corner that already had to do it.
Two more failures are the filesystem itself, not the checkout, so they are
skipped on Windows next to the guards already there for the same reason:
symlinking a `node_modules` needs Developer Mode, and the case-collision case
cannot be written on a case-insensitive filesystem.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every entry in the day scale was between 24px and 36px, and the picker scale between 24px and 36px tall. All of it is under what a finger can reliably hit, and this calendar renders inside a drawer on a phone — where it is hit with a thumb, not a mouse. The scale is now mobile-first: a 44px target on a touch screen, with the compact desktop cell arriving at `sm:`. The `sm:` half is the scale exactly as it was, so nothing changes on a pointer device — measured at 32x32 for `md` before and after. The 0.5 margin moves behind the same breakpoint, because at 44px it pushed seven columns past a phone's width; without it the grid measures 358px inside a 390px viewport with no horizontal scroll. The guard checks all four sizes, not one: a variant can only narrow the `sm:` half now, and the test goes red if any size drops the base again. This is what `B24DateTimePicker` sits on, so its calendar step was small under the thumb while its own time grid was already 44px. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc
`@nuxtjs/mcp-toolkit` re-exports `completable` straight from the MCP SDK. With the SDK left external, the dev bundle imported that module for side effects only while keeping the name in its namespace object, so the server threw `ReferenceError: completable is not defined` before it served a request — every docs page came back as a 500. Inlining the SDK gives the re-export a binding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…/date-time-picker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linked issue
Rebuilds what #55 attempted. That branch was opened before
mainwas re-rooted, so it shares no merge base with it —git merge-baseexits 1 — and could neither rebase nor merge. It was closed and used here as a specification rather than as a base.Follow-ups filed from this PR: #582 (native review of thirteen locales), #583 (nothing catches an empty API section).
Type of change
revert(Scope): ...)Description
B24DateTimePickerpicks a date and a time in one control: a calendar, then a time grid, with a preset column beside them. It renders in aB24Popoveron pointer-sized screens and aB24Draweron small ones, through the existinguseDevicecomposable.Public surface
modelValue,defaultValue,open,defaultOpen,dateOnly,minuteStep,locale,placeholder,presets,hidePresets,format,color,size,disabled,icon,timeIcon, plus pass-through objectspopover,drawer,calendar,input.update:modelValue,change,update:open.default(replaces the trigger),presets,preset,time-header.src/theme/date-time-picker.ts: 20 slots,color(5 values) andsize(xs/sm/md/lg) variants, defaultsmd/air-primary.Decisions worth a reviewer's attention
The trigger is a
B24Input, not a wrapper around one. The first version wrapped a readonly input in a clickablediv.axerejected it twice:aria-allowed-attr, becausearia-expandedsat on a role-less element; thennested-interactive, once arole="button"was added and the real<input>was still inside it.B24Inputforwards fall-through attributes onto its<input>, so the popover'saria-haspopup/aria-expandedland on a genuine control and the wrapper is gone.shallowReffor the value, notref. Vue's deep unwrapping widens theDateValueunion of class instances into a structural object that no longer matches the type. Measured: swapping the one word and runningpnpm typecheckproduces sevenTS2322/TS2345errors in this file. Date values are immutable, so there is nothing to track deeply anyway. The component carries noas anyand noas unknown as DateValue.minuteStepis clamped, not trusted. 1…30, with a non-finite value falling back to the default rather than rendering an empty or unbounded grid. Covered at five values, not one —undefined, 15, 90, 0,NaN— because a guard checked at one value is not a guard checked.Presets resolve once per render. A preset's
valuemay be a function so that "today" stays today across a long session;resolvedPresetscalls it a single time and the test asserts the call count, so rendering and applying cannot disagree.dateOnlydrops the time rather than zeroing it. The value stays aCalendarDate, so it carries no time at all instead of an implied00:00, and applying a preset underdateOnlydiscards the preset's time too.Documentation —
docs/content/docs/2.components/date-time-picker.mdwith five runnable examples, both playgrounds, and the page registered indocs/nuxt.config.tsand in the skill index. The scaffolder's Nuxt UI and reka-ui front-matter links were removed: reka-ui has no such component and the generated URL returns 404.What the review found
A five-reviewer panel ran over the first four commits. Every finding below was reproduced before being acted on, and the fixes are in
02fe7e09andbddc6ae3.The end-of-week preset resolved to the wrong day. It was derived from
startOfWeek(…).add({ days: 4 }), which reads as Friday only where the week starts on Monday. Measured: Thursday underen— this repository's own default locale — and Wednesday underar, where it could resolve to the current day and the button would do nothing. The wrong day was already sitting in the committed snapshot, accepted because nothing asserted the weekday. The index is now anchored toen-US, so Friday is 5 everywhere.Seven locales named the weekend, not the end of the week —
ja,sc,tc,th,id,ms,la— which is a different day from the one the preset picks. The same class of error was fixed forde/fr/it/br/trduring #55; it came back because the preset itself had no settled meaning. Corrected here; the remaining thirteen unreviewed locales are #582, and the replacements are the author's, not a native speaker's.openwas not a controlled prop. It was mirrored into a ref that every internal handler wrote to, so picking a date closed the picker even while the parent held the prop attrue. It now reads through the prop and asks to close by emitting.A throwing preset factory took the whole component down.
presets[].valuemay be consumer-supplied and is called during render; an exception escaped the computed the template reads. Under SSR that rejected the page render outright, and on the client it replaced the component — working calendar included — with an empty node. One bad preset now drops out of the list with a warning.A
localestringIntlrejects threw before the picker could open. The prop feeds fourIntl.DateTimeFormatconstructions, one of them on the always-visible trigger, so a malformed tag was fatal on first render. It falls back toen.The spec did not pin the clock.
Calendar,InputDateandInputTimeall callvi.setSystemTime; this one did not, so the preset hints wrote the day it was authored into the snapshots six times. They would have gone red the next morning.The API section on the docs page was empty.
interface DateTimePickerProps {}, and the same for slots and emits, whiledocs:generatereported 1100 routes with no failures. The cause was not in the component: thecomponentMetatransformer indocs/nuxt.config.tsrewrites theb24uislot-prop token anywhere it appears, including where it is an object key in an expression, and the result does not parse.vue-component-metathen returns nothing for the component, silently.B24ThemeandB24DashboardSearchButtonwere being rewritten the same way, andB24EditorDragHandle'sonClickslot prop was being swallowed by the same pattern crossing a brace. Bounding it to the plain<script>block and forbidding the run from crossing a brace keeps 119 of the 125 sites it hit and drops exactly the six it was damaging. That the failure is invisible at all is #583.Nine tests added, each checked against a mutation of the code it covers: accessibility on the time step (the first check never opened it),
change,update:open, the controlledopen, the resolved weekday across three locales, the throwing factory, the rejected locale, a time step opened without a value, and thedefaultValue/defaultOpenseeds.Not acted on. One reviewer read the "Inside a FormField" example as decorative, on the grounds that the component does not call
useFormFielditself. Measured instead: inside aB24FormField, the picker's inner input receivesid="reka-popover-trigger-…"with the label'sforpointing at it andaria-describedby="…-description"— the association works, throughB24Input's ownuseFormField.disableddoes not propagate, but a bareB24Inputin the same disabledB24FormFielddoes not receive it either, so that is not this component's behaviour to fix. Separately, a reviewer noted that av-bindspread placed after individual binds can overwrite them withundefined; that ordering is the established convention at every forwarding site in the kit, so changing it here alone would make this component the odd one out.What this does not do
min-value/max-value/is-date-disabled— those reach the calendar through thecalendarpass-through prop only.defineModelwas not used; the component keeps the explicit prop/emit pair the rest of the kit uses.Checklist
Local gate on
bddc6ae3:lint0,typecheck0,pnpm test353 files / 8049 passed / 6 skipped,docs:generatefrom a cold cache 1100 routes with no prerender errors and the API section populated (21 props, 4 slots, 3 emits).🤖 Generated with Claude Code
https://claude.ai/code/session_012MsMuj8Fic9tjWVjyEyrxc