feat: default theme layout with tab bar + page nav types#37
feat: default theme layout with tab bar + page nav types#37
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds prev/next page navigation to types, source, API, SSR, and client hydration; updates page context and components to carry nav links; overhauls default theme layout, TOC, and page styles; simplifies search/theme UI; switches docs theme and adds a new package dependency. Changes
Sequence Diagram(s)sequenceDiagram
participant Browser
participant Server
participant PageTree
participant ClientApp as Client App
participant PageAPI as /api/page
Browser->>Server: Request page (SSR)
Server->>PageTree: getPage(slug) and getPageNav(slug)
PageTree-->>Server: pageData + nav(prev/next)
Server-->>Browser: HTML + __PAGE_DATA__ (includes prev/next)
Browser->>ClientApp: Hydrate (reads __PAGE_DATA__)
ClientApp->>PageContext: setPage(page + prev/next)
alt user navigates to another doc
ClientApp->>PageAPI: fetch /api/page?slug
PageAPI->>PageTree: getPage + getPageNav
PageTree-->>PageAPI: page + nav
PageAPI-->>ClientApp: payload with prev/next
ClientApp-->>Layout: render prev/next IconButtons
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
packages/chronicle/src/themes/default/Layout.module.css (1)
36-67: Tab styles look good; minor consideration onborder: 0.5px.Sub-pixel borders (
0.5px) render inconsistently across browsers/DPRs — on 1x displays some browsers will round to 0 and the border disappears. If the intent is a thinner-looking divider, consider1pxwith a lighter border color token, or rely ontransform: scaleY(0.5)patterns. Not blocking.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/themes/default/Layout.module.css` around lines 36 - 67, Replace the sub-pixel border in the .tab rule: avoid using "0.5px" (in Layout.module.css .tab) because it can disappear on some DPRs; change it to "1px" and pair it with a lighter border token (e.g., use the existing var(--rs-color-border-*-secondary) style) or alternatively implement a visual thinner divider using transform scaling on a pseudo-element (apply to .tab or .tabActive as appropriate) so the thin visual is preserved across browsers and DPRs.packages/chronicle/src/server/entry-server.tsx (1)
38-60:prev/nexthardcoded tonull— track the follow-up.Per the PR description this is intentional placeholder wiring until the backend computes adjacent-page links from the page tree. Worth leaving a
TODOhere (and symmetric sites) referencing the tracking issue so this doesn't silently ship to consumers expecting real navigation data.Happy to open a follow-up issue to compute
prev/nextfromgetPageTree()+ currentslugif you'd like.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/server/entry-server.tsx` around lines 38 - 60, The pageData currently sets prev and next to null and embeddedData mirrors those nulls; add a clear TODO comment in the pageData/embeddedData block noting these are intentional placeholders and reference the tracking issue (e.g. "TODO: compute prev/next from getPageTree() using current slug — see tracking issue #<ID>") so consumers and future maintainers know to replace the hardcoded nulls with adjacent-page links computed from getPageTree() + slug.packages/chronicle/src/server/entry-client.tsx (1)
13-22:EmbeddedDatadrifts fromPageshape — consider reusing types.
EmbeddedDataduplicates fields that now exist onPage/PageNav. As more nav metadata lands (e.g., section, breadcrumbs), keeping two hand-maintained shapes in sync is error-prone. Consider typing asPick<Page, 'slug' | 'frontmatter' | 'prev' | 'next'> & { config; tree; relativePath; originalPath? }or defining a sharedEmbeddedPagePayloadin@/types.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/server/entry-client.tsx` around lines 13 - 22, The EmbeddedData interface duplicates fields already defined on Page/PageNav (slug, frontmatter, prev, next); replace the handwritten shape by reusing existing types—e.g., change EmbeddedData to compose Pick<Page, 'slug' | 'frontmatter' | 'prev' | 'next'> & { config: ChronicleConfig; tree: Root; relativePath: string; originalPath?: string } or create/use a shared EmbeddedPagePayload type in the types package and import it; update imports to pull Page (or EmbeddedPagePayload) and PageNavLink as needed and remove the duplicated field declarations from EmbeddedData.packages/chronicle/src/themes/default/Layout.tsx (2)
1-9: Nit: merge duplicate@heroicons/react/24/outlineimports.Lines 1 and 2–5 import from the same module in two separate statements.
♻️ Proposed tidy-up
-import { RectangleStackIcon } from '@heroicons/react/24/outline'; import { + RectangleStackIcon, DocumentTextIcon, CodeBracketSquareIcon } from '@heroicons/react/24/outline'; -import { +import { Flex, Sidebar } from '@raystack/apsara';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/themes/default/Layout.tsx` around lines 1 - 9, There are two import statements pulling icons from the same module; merge them into a single import from '@heroicons/react/24/outline' that includes RectangleStackIcon, DocumentTextIcon, and CodeBracketSquareIcon to avoid duplicate imports (leave the existing imports for Flex and Sidebar from '@raystack/apsara' unchanged); update the import block in Layout.tsx so all three icon symbols are imported in one statement.
77-100: Accessibility: tabs semantics and focus.The tab bar uses
<nav>+RouterLinks styled as tabs. If these are genuinely tabs (mutually exclusive sections), considerrole="tablist"/role="tab"+aria-current="page"on the active link — at minimum addaria-current="page"to the active tab so assistive tech announces selection. Verify keyboard focus states are visible under the new.tab/.tabActiverules (no:focus-visiblestyles are defined in the module).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/themes/default/Layout.tsx` around lines 77 - 100, The tab bar currently renders RouterLink elements as tabs but lacks proper ARIA and focus semantics; update the nav and links in Layout.tsx to add role="tablist" on the nav, role="tab" on each RouterLink, and set aria-current="page" on the active tab (use the existing isApiRoute logic for the API links and the root tab check for '/'), and ensure the styles.tab / styles.tabActive CSS module includes visible focus styles (or add a :focus-visible rule) so keyboard focus is clearly visible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chronicle/src/lib/page-context.tsx`:
- Around line 108-113: The API response may omit prev/next so normalize them to
match Page['prev'] (PageNavLink | null) before calling setPage: when handling
the promise result in the .then callback (the function that calls loadMdx,
setErrorStatus and setPage with slug and data.frontmatter), coerce data.prev and
data.next to null if they are undefined (e.g., const prev = data.prev ?? null;
const next = data.next ?? null) and pass those normalized values into setPage to
preserve the SSR-hydrated shape and avoid undefined prev/next on client
navigation.
In `@packages/chronicle/src/themes/default/Layout.tsx`:
- Around line 86-95: The API tabs all use the shared boolean isApiRoute causing
every API entry from config.api to be marked active; update the tab active logic
to compute per-entry activity by checking the current pathname against each
api.basePath (e.g., replace the shared isApiRoute in the RouterLink className
expression with a per-item check like pathname.startsWith(api.basePath) or a
helper like isApiBasePath(api.basePath)); ensure the RouterLink rendering for
each api uses that per-api active boolean when toggling styles.tabActive (and
revisit the Docs tab's !isApiRoute usage if you later add non-/apis sections).
---
Nitpick comments:
In `@packages/chronicle/src/server/entry-client.tsx`:
- Around line 13-22: The EmbeddedData interface duplicates fields already
defined on Page/PageNav (slug, frontmatter, prev, next); replace the handwritten
shape by reusing existing types—e.g., change EmbeddedData to compose Pick<Page,
'slug' | 'frontmatter' | 'prev' | 'next'> & { config: ChronicleConfig; tree:
Root; relativePath: string; originalPath?: string } or create/use a shared
EmbeddedPagePayload type in the types package and import it; update imports to
pull Page (or EmbeddedPagePayload) and PageNavLink as needed and remove the
duplicated field declarations from EmbeddedData.
In `@packages/chronicle/src/server/entry-server.tsx`:
- Around line 38-60: The pageData currently sets prev and next to null and
embeddedData mirrors those nulls; add a clear TODO comment in the
pageData/embeddedData block noting these are intentional placeholders and
reference the tracking issue (e.g. "TODO: compute prev/next from getPageTree()
using current slug — see tracking issue #<ID>") so consumers and future
maintainers know to replace the hardcoded nulls with adjacent-page links
computed from getPageTree() + slug.
In `@packages/chronicle/src/themes/default/Layout.module.css`:
- Around line 36-67: Replace the sub-pixel border in the .tab rule: avoid using
"0.5px" (in Layout.module.css .tab) because it can disappear on some DPRs;
change it to "1px" and pair it with a lighter border token (e.g., use the
existing var(--rs-color-border-*-secondary) style) or alternatively implement a
visual thinner divider using transform scaling on a pseudo-element (apply to
.tab or .tabActive as appropriate) so the thin visual is preserved across
browsers and DPRs.
In `@packages/chronicle/src/themes/default/Layout.tsx`:
- Around line 1-9: There are two import statements pulling icons from the same
module; merge them into a single import from '@heroicons/react/24/outline' that
includes RectangleStackIcon, DocumentTextIcon, and CodeBracketSquareIcon to
avoid duplicate imports (leave the existing imports for Flex and Sidebar from
'@raystack/apsara' unchanged); update the import block in Layout.tsx so all
three icon symbols are imported in one statement.
- Around line 77-100: The tab bar currently renders RouterLink elements as tabs
but lacks proper ARIA and focus semantics; update the nav and links in
Layout.tsx to add role="tablist" on the nav, role="tab" on each RouterLink, and
set aria-current="page" on the active tab (use the existing isApiRoute logic for
the API links and the root tab check for '/'), and ensure the styles.tab /
styles.tabActive CSS module includes visible focus styles (or add a
:focus-visible rule) so keyboard focus is clearly visible.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 324e5e64-cd4d-411d-9155-dc48131055f8
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
docs/chronicle.yamlpackage.jsonpackages/chronicle/src/lib/page-context.tsxpackages/chronicle/src/pages/DocsPage.tsxpackages/chronicle/src/server/entry-client.tsxpackages/chronicle/src/server/entry-server.tsxpackages/chronicle/src/themes/default/Layout.module.csspackages/chronicle/src/themes/default/Layout.tsxpackages/chronicle/src/types/content.ts
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
packages/chronicle/src/themes/default/Layout.module.css (1)
103-124: Hardcoded shadow color and near-no-opbackdrop-filter.
- Lines 110–111:
rgba(0, 0, 0, 0.04)won't render meaningfully in dark themes; consider a token-based shadow (e.g.,var(--rs-shadow-...)) to stay theme-aware.- Lines 123 / 31:
backdrop-filter: blur(1px)is visually imperceptible. Either increase the radius or drop it to avoid paying for an unused GPU pass.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/themes/default/Layout.module.css` around lines 103 - 124, Update the theme-unaware hardcoded shadow and the near-no-op backdrop filter: replace the rgba(...) shadow used in the .card rule with a theme token (e.g., var(--rs-shadow-*, or an appropriate existing shadow variable) so the elevation responds to dark/light themes and accessibility settings, and for .subNav either increase the blur radius (e.g., to a perceptible value) or remove the backdrop-filter property to avoid unnecessary GPU work—change the rules in the .card and .subNav selectors accordingly.packages/chronicle/src/themes/default/Page.module.css (1)
33-45: Minor: redundant/odd values in content typography.
- Line 34:
margin-top: 0on.content h1is redundant with the:first-childreset above and also removes top spacing for any non-firsth1(uncommon, but worth a conscious call).- Line 44:
line-height: 171.429%is a computed literal — prefer a design token (e.g.,var(--rs-line-height-regular)) or a clean unitless value like1.71for readability and consistency with the rest of the file.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/themes/default/Page.module.css` around lines 33 - 45, Remove the redundant margin-top: 0 from the .content h1 rule (or replace it with a comment/intent if you explicitly need to override the :first-child reset) to avoid unintentionally removing top spacing for non-first h1s; and change the .content p rule's line-height: 171.429% to a design token or a unitless value (e.g., var(--rs-line-height-regular) or 1.71) so it matches the file's styling conventions and improves readability.packages/chronicle/src/themes/default/Layout.tsx (1)
121-150: Consider hiding the sub-nav on pages without navigation data.On routes where
pageisnull(404, non-content routes, API index) both IconButtons will be disabled and Breadcrumbs may render empty — producing a visually empty sub-nav bar. Low priority, but you may want to conditionally render<nav className={styles.subNav}>when there is meaningful content to show, or collapse it to avoid an empty 48px bar.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/themes/default/Layout.tsx` around lines 121 - 150, The sub-nav should be omitted when there's no navigation to display; update the Layout component to only render the <nav className={styles.subNav}> block when there is meaningful content (e.g. prev or next exist, or Breadcrumbs would render). Compute a boolean like hasNav = Boolean(prev || next || (slug && tree && tree.length)) and wrap the existing nav (including the IconButton group and <Breadcrumbs slug={slug} tree={tree} />) in a conditional render using that flag so the empty 48px sub-nav is not shown on pages with no page data.packages/chronicle/src/components/ui/client-theme-switcher.tsx (1)
19-34: Minor: hydration placeholder causes a brief layout shift.Returning
nulluntilisClientflips avoids SSR/CSR theme mismatch, but the sidebar navbar's right-side actions row visibly reflows on mount as this button appears. Consider rendering a fixed-size placeholder (or usingvisibility: hiddenon a sized wrapper) to reserve space, e.g.:💡 Suggestion
- if (!isClient) return null - - const isDark = resolvedTheme === 'dark' + const isDark = resolvedTheme === 'dark' + if (!isClient) { + return <IconButton size={3} aria-hidden tabIndex={-1} style={{ visibility: 'hidden' }} /> + }Also note: the
sizeprop only controls the inner icon dimensions; the outerIconButtonis pinned tosize={3}. If that's intentional, it may be worth documenting or dropping the prop.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/components/ui/client-theme-switcher.tsx` around lines 19 - 34, When isClient is false the component returns null causing a layout shift on mount; instead render a fixed-size placeholder that matches the IconButton's outer dimensions so space is reserved (for example render the same IconButton structure with visibility:hidden or a sized wrapper) inside the ClientThemeSwitcher (use the existing isClient check and IconButton/size props to mirror size), and either align the outer IconButton's size with the incoming size prop or document why IconButton uses size={3} if you intentionally keep the inner icon size independent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chronicle/src/components/ui/search.tsx`:
- Around line 55-62: The keyboard shortcut (Cmd/Ctrl+K) is still wired in the
useEffect but the visual hint was removed; add a discoverability hint to the
IconButton by setting a title attribute (e.g., title="Search (⌘K)") on the
IconButton component used in this file so users see the shortcut on hover; keep
the existing props (size, aria-label, onClick => setOpen(true), className) and
only add the title string (you can include both Mac and Win hints if desired).
In `@packages/chronicle/src/lib/source.ts`:
- Around line 115-118: toLink currently forces PageNavLink.title by doing
String(p.name) which will stringify ReactNode names to "[object Object]";
instead guard and produce a proper string title: if p.name is a string use it,
otherwise resolve the page by URL from source.getPages() (or the pages array)
and use extractFrontmatter(match).title as the fallback (and finally fallback to
an empty string); update the toLink mapping (referencing toLink, pages,
PageNavLink.title, Item.name, source.getPages(), and extractFrontmatter) to
implement this lookup and type-guard so non-string ReactNode names do not
produce "[object Object]".
In `@packages/chronicle/src/themes/default/Layout.tsx`:
- Around line 47-50: Breadcrumbs is always fed the docs tree so on API routes
getBreadcrumbItems can't find the path; update the Layout component to use the
isApiRoute flag (detected at isApiRoute) to either hide the Breadcrumbs or pass
the API tree instead of the docs tree: when isApiRoute is true, supply the API
page tree (the API tree variable) to the Breadcrumbs call (or skip rendering
Breadcrumbs entirely on API routes), ensuring getBreadcrumbItems receives the
correct tree for the slug computed by useMemo.
In `@packages/chronicle/src/themes/default/Page.tsx`:
- Around line 7-15: The theme no longer renders the table of contents because
the Toc component is not wired into the default layout; reintegrate Toc into the
layout so pages render a TOC (or document its removal). Update the default
theme’s Layout component (where Breadcrumbs was added) to import and render the
Toc component alongside or within the same container used for Breadcrumbs
(reference Toc and Breadcrumbs components and the Layout and Page/ThemePageProps
rendering flow), pass the page or page.toc data as needed, and ensure any
CSS/classes (styles.toc or styles.sidebar) are applied so the TOC appears
without breaking existing layout.
---
Nitpick comments:
In `@packages/chronicle/src/components/ui/client-theme-switcher.tsx`:
- Around line 19-34: When isClient is false the component returns null causing a
layout shift on mount; instead render a fixed-size placeholder that matches the
IconButton's outer dimensions so space is reserved (for example render the same
IconButton structure with visibility:hidden or a sized wrapper) inside the
ClientThemeSwitcher (use the existing isClient check and IconButton/size props
to mirror size), and either align the outer IconButton's size with the incoming
size prop or document why IconButton uses size={3} if you intentionally keep the
inner icon size independent.
In `@packages/chronicle/src/themes/default/Layout.module.css`:
- Around line 103-124: Update the theme-unaware hardcoded shadow and the
near-no-op backdrop filter: replace the rgba(...) shadow used in the .card rule
with a theme token (e.g., var(--rs-shadow-*, or an appropriate existing shadow
variable) so the elevation responds to dark/light themes and accessibility
settings, and for .subNav either increase the blur radius (e.g., to a
perceptible value) or remove the backdrop-filter property to avoid unnecessary
GPU work—change the rules in the .card and .subNav selectors accordingly.
In `@packages/chronicle/src/themes/default/Layout.tsx`:
- Around line 121-150: The sub-nav should be omitted when there's no navigation
to display; update the Layout component to only render the <nav
className={styles.subNav}> block when there is meaningful content (e.g. prev or
next exist, or Breadcrumbs would render). Compute a boolean like hasNav =
Boolean(prev || next || (slug && tree && tree.length)) and wrap the existing nav
(including the IconButton group and <Breadcrumbs slug={slug} tree={tree} />) in
a conditional render using that flag so the empty 48px sub-nav is not shown on
pages with no page data.
In `@packages/chronicle/src/themes/default/Page.module.css`:
- Around line 33-45: Remove the redundant margin-top: 0 from the .content h1
rule (or replace it with a comment/intent if you explicitly need to override the
:first-child reset) to avoid unintentionally removing top spacing for non-first
h1s; and change the .content p rule's line-height: 171.429% to a design token or
a unitless value (e.g., var(--rs-line-height-regular) or 1.71) so it matches the
file's styling conventions and improves readability.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 26555773-af54-4944-8cd9-4f4fc0f16ecd
📒 Files selected for processing (10)
packages/chronicle/src/components/ui/client-theme-switcher.tsxpackages/chronicle/src/components/ui/search.module.csspackages/chronicle/src/components/ui/search.tsxpackages/chronicle/src/lib/source.tspackages/chronicle/src/server/api/page.tspackages/chronicle/src/server/entry-server.tsxpackages/chronicle/src/themes/default/Layout.module.csspackages/chronicle/src/themes/default/Layout.tsxpackages/chronicle/src/themes/default/Page.module.csspackages/chronicle/src/themes/default/Page.tsx
💤 Files with no reviewable changes (1)
- packages/chronicle/src/components/ui/search.module.css
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/chronicle/src/server/entry-server.tsx
There was a problem hiding this comment.
Pull request overview
Restructures Chronicle’s default theme layout to introduce a section tab bar + updated sidebar/nav chrome, and adds typed prev/next navigation data to the Page model (with server/client wiring stubbed).
Changes:
- Introduce
PageNavLink/PageNavtypes and extendPagewith backend-providedprev/nextlinks. - Redesign the default theme layout (remove top navbar, add tab bar + card wrapper + new TOC UI).
- Switch docs to the default theme and add
std-envas a root dependency to unblock Nitro runtime resolution.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/chronicle/src/types/content.ts | Adds PageNavLink/PageNav and extends Page with prev/next. |
| packages/chronicle/src/lib/source.ts | Adds getPageNav() implementation using the page tree. |
| packages/chronicle/src/server/entry-server.tsx | Embeds prev/next into SSR page payload; calls getPageNav. |
| packages/chronicle/src/server/entry-client.tsx | Hydrates prev/next from embedded payload into client page model. |
| packages/chronicle/src/server/api/page.ts | Extends /api/page response with prev/next. |
| packages/chronicle/src/lib/page-context.tsx | Updates context page typing to Page and populates prev/next on client fetch. |
| packages/chronicle/src/pages/DocsPage.tsx | Passes the full page object through to the theme page component. |
| packages/chronicle/src/themes/default/Layout.tsx | Replaces top navbar with sidebar header + tab bar + subnav containing prev/next + breadcrumbs. |
| packages/chronicle/src/themes/default/Layout.module.css | Styles the new layout structure (sidebar header, tab bar, card/subnav). |
| packages/chronicle/src/themes/default/Page.tsx | Removes breadcrumbs from page body (now rendered in layout). |
| packages/chronicle/src/themes/default/Page.module.css | Adds typography/layout tweaks for headings and paragraphs. |
| packages/chronicle/src/themes/default/Toc.tsx | Replaces text TOC with marker rail + hover panel TOC. |
| packages/chronicle/src/themes/default/Toc.module.css | Styles marker rail + hover-revealed panel. |
| packages/chronicle/src/components/ui/search.tsx | Switches search trigger from button to icon button and removes inline shortcut UI. |
| packages/chronicle/src/components/ui/search.module.css | Removes trigger/kbd styles; keeps dialog styling. |
| packages/chronicle/src/components/ui/client-theme-switcher.tsx | Replaces ThemeSwitcher with a custom icon toggle using useTheme(). |
| docs/chronicle.yaml | Switches docs example theme from paper to default. |
| package.json | Adds std-env root dependency for Nitro runtime compatibility. |
| bun.lock | Lockfile update for std-env. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| .toc:hover .markers { | ||
| opacity: 0; | ||
| pointer-events: none; | ||
| } | ||
|
|
||
| .marker { | ||
| display: block; | ||
| height: 2px; | ||
| background: var(--rs-color-border-base-secondary); | ||
| border-radius: 1px; | ||
| transition: | ||
| width 0.15s ease, | ||
| background 0.15s ease; | ||
| } | ||
|
|
||
| .marker:hover { | ||
| background: var(--rs-color-foreground-base-secondary); | ||
| } | ||
|
|
||
| .markerActive { | ||
| background: var(--rs-color-border-base-emphasis); | ||
| } | ||
|
|
||
|
|
||
| .panel { | ||
| position: absolute; | ||
| top: 50%; | ||
| right: 0; | ||
| transform: translateY(-50%) translateX(8px); | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: var(--rs-space-2); | ||
| min-width: 200px; | ||
| padding: var(--rs-space-3) 0; | ||
| background: var(--rs-color-background-base-primary); | ||
| border: 0.5px solid var(--rs-color-border-base-primary); | ||
| border-radius: var(--rs-radius-4); | ||
| box-shadow: var(--rs-shadow-soft); | ||
| opacity: 0; | ||
| pointer-events: none; | ||
| transition: | ||
| opacity 150ms ease, | ||
| transform 150ms ease; | ||
| } | ||
|
|
||
| .toc:hover .panel { | ||
| opacity: 1; | ||
| pointer-events: auto; | ||
| transform: translateY(-50%) translateX(0); | ||
| } |
There was a problem hiding this comment.
The TOC panel/markers are only toggled on :hover, so keyboard users who tab into the TOC won’t ever see the expanded panel. Consider adding :focus-within (and/or :focus-visible) variants that mirror the hover behavior so the panel becomes visible when any TOC link receives focus.
b634599 to
cd86f5e
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
packages/chronicle/src/themes/default/Toc.tsx (1)
39-46:markerWidthassumes monospace character metrics.Using
length * 1px + 8pxas a width is a very rough proxy for text width with proportional fonts and unicode (CJK, emoji, combining marks); the effect is mostly cosmetic but results can look off. If the visual intent is "longer heading → longer marker," consider bucketing by depth or clamping to a small set of widths instead of per-character math.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/themes/default/Toc.tsx` around lines 39 - 46, markerWidth currently multiplies character count by MARKER_PER_CHAR (using MARKER_BASE, MARKER_PER_CHAR, MARKER_MAX) which assumes monospace and yields poor results for proportional/unicode text; change it to compute a bucketed width instead (e.g., map nodeToText(title).length to a small set of discrete widths or derive from heading depth) so that widths step through a few clamped sizes rather than per-character pixels; update the markerWidth(title: ReactNode) implementation to produce one of a few preset widths (using MARKER_BASE and MARKER_MAX as min/max and a small set of increments) and keep references to MARKER_BASE, MARKER_PER_CHAR, MARKER_MAX or replace MARKER_PER_CHAR with a BUCKET_STEP/thresholds constant for clarity.packages/chronicle/src/lib/page-context.tsx (1)
57-59:isApisRouteis defined but never referenced.The helper looks intended to classify API routes but isn't wired up here. Either drop it or use it in place of the ad-hoc
pathname.startsWith('/apis')check inLayout.tsx(line 48), which additionally matches unrelated paths like/apispecs.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/lib/page-context.tsx` around lines 57 - 59, The helper isApisRoute(pathname: string) is defined but unused; replace the ad-hoc check pathname.startsWith('/apis') in Layout.tsx (the conditional around rendering/layout logic) with a call to isApisRoute(pathname) so only true API routes like '/apis' or '/apis/*' match (or alternatively delete the unused isApisRoute if you prefer to keep the existing matching behavior). Update imports/exports if needed so isApisRoute from page-context.tsx is referenced by Layout.tsx, and run tests to confirm no other callers break.packages/chronicle/src/themes/default/Layout.module.css (1)
22-46: Four declared CSS classes aren't referenced inLayout.tsx.
.sidebarNavbar(22-32),.sidebarNavLogo(34-36),.sidebarNavActions(38-42), and.sidebarMain(44-46) appear unused — the currentLayout.tsxuses.sidebarHeader+ inlineFlexgap for the sidebar top bar, andSidebar.Mainis rendered without a custom class. Consider removing them to keep the stylesheet lean, or wire them up if they're intended for a follow-up commit.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/themes/default/Layout.module.css` around lines 22 - 46, The stylesheet defines .sidebarNavbar, .sidebarNavLogo, .sidebarNavActions, and .sidebarMain but Layout.tsx currently uses Sidebar.Header (or a .sidebarHeader) and inline Flex gap instead, so either remove those four unused classes from Layout.module.css or wire them up: in Layout.tsx replace the current Sidebar.Header wrapper with className={styles.sidebarNavbar}, give the logo element className={styles.sidebarNavLogo}, wrap the header action buttons in an element with className={styles.sidebarNavActions}, and pass className={styles.sidebarMain} to Sidebar.Main so the declared CSS is actually applied.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chronicle/src/components/ui/search.tsx`:
- Line 61: The title string "Search (⌘K)" is Mac-specific; update the Search
component's title prop (currently set as title='Search (⌘K)') to be
platform-aware by computing the shortcut label on mount and using that value, or
replace it with a platform-agnostic hint like "Search (Ctrl/⌘+K)"; look for the
Search component in this file (title='Search (⌘K)') and either derive the label
via navigator.platform or userAgentData in a useEffect/useMemo and set
title={shortcutLabel} or change the literal to "Search (Ctrl/⌘+K)" so
Windows/Linux users see the correct hint.
In `@packages/chronicle/src/themes/default/Layout.tsx`:
- Line 48: The isApiRoute check currently uses pathname.startsWith('/apis')
which incorrectly matches paths like '/apispecs'; update the logic in Layout.tsx
(the isApiRoute definition) to use the same semantic as isApiBase (i.e., match
either pathname === '/apis' or pathname.startsWith('/apis/')) or import and
reuse the existing isApisRoute helper exported from page-context.tsx so the
Breadcrumbs suppression (the conditional that uses isApiRoute) only fires for
the actual /apis base and its true subroutes.
In `@packages/chronicle/src/themes/default/Toc.module.css`:
- Around line 1-7: The .toc class is fixed to the viewport with no responsive
rules so it overlaps content on narrow viewports; update Toc.module.css to add
responsive handling by adding a media query (e.g., for max-width breakpoints)
that hides or repositions .toc on small screens, or change the positioning
strategy for .toc to anchor to the layout (e.g., use relative/sticky within the
page/container) and lower/remove the z-index as needed; target the .toc selector
and adjust the display/position/z-index inside the new media query or alternate
positioning rule so the TOC does not overlap article content on narrow
viewports.
In `@packages/chronicle/src/themes/default/Toc.tsx`:
- Around line 48-97: The TOC panel is currently only revealed on hover which
hides labels from keyboard and touch users; update the TocContent component and
CSS so the panel is also revealed on focus and can be toggled on touch: add
tabIndex={0} to the aside rendered by TocContent (so it can receive keyboard
focus) and implement a simple toggle state (e.g., isOpen) with a clickable
button/indicator that sets aria-expanded on the toggle and adds an "open" class
when active; in Toc.module.css update the reveal rule from .toc:hover to also
match .toc:focus-within and .toc.open (and ensure focus styles are preserved) so
keyboard users (focus-within) and touch users (toggle adds .open) can see the
panel.
- Around line 53-95: The marker column is creating duplicate focusable anchors;
update the marker anchors rendered in the styles.markers map (the <a> produced
inside items.map that uses styles.marker and markerWidth) to be non-interactive
by adding aria-hidden="true" and tabIndex={-1} (or set the wrapper div
styles.markers to aria-hidden="true") so they are ignored by assistive tech and
keyboard navigation, and keep the panel <nav> (styles.panel / styles.panelList
and its panelItem anchors) as the single accessible navigation.
---
Nitpick comments:
In `@packages/chronicle/src/lib/page-context.tsx`:
- Around line 57-59: The helper isApisRoute(pathname: string) is defined but
unused; replace the ad-hoc check pathname.startsWith('/apis') in Layout.tsx (the
conditional around rendering/layout logic) with a call to isApisRoute(pathname)
so only true API routes like '/apis' or '/apis/*' match (or alternatively delete
the unused isApisRoute if you prefer to keep the existing matching behavior).
Update imports/exports if needed so isApisRoute from page-context.tsx is
referenced by Layout.tsx, and run tests to confirm no other callers break.
In `@packages/chronicle/src/themes/default/Layout.module.css`:
- Around line 22-46: The stylesheet defines .sidebarNavbar, .sidebarNavLogo,
.sidebarNavActions, and .sidebarMain but Layout.tsx currently uses
Sidebar.Header (or a .sidebarHeader) and inline Flex gap instead, so either
remove those four unused classes from Layout.module.css or wire them up: in
Layout.tsx replace the current Sidebar.Header wrapper with
className={styles.sidebarNavbar}, give the logo element
className={styles.sidebarNavLogo}, wrap the header action buttons in an element
with className={styles.sidebarNavActions}, and pass
className={styles.sidebarMain} to Sidebar.Main so the declared CSS is actually
applied.
In `@packages/chronicle/src/themes/default/Toc.tsx`:
- Around line 39-46: markerWidth currently multiplies character count by
MARKER_PER_CHAR (using MARKER_BASE, MARKER_PER_CHAR, MARKER_MAX) which assumes
monospace and yields poor results for proportional/unicode text; change it to
compute a bucketed width instead (e.g., map nodeToText(title).length to a small
set of discrete widths or derive from heading depth) so that widths step through
a few clamped sizes rather than per-character pixels; update the
markerWidth(title: ReactNode) implementation to produce one of a few preset
widths (using MARKER_BASE and MARKER_MAX as min/max and a small set of
increments) and keep references to MARKER_BASE, MARKER_PER_CHAR, MARKER_MAX or
replace MARKER_PER_CHAR with a BUCKET_STEP/thresholds constant for clarity.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: fa4772fe-f929-4218-b29e-e9b312e5d206
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (18)
docs/chronicle.yamlpackage.jsonpackages/chronicle/src/components/ui/client-theme-switcher.tsxpackages/chronicle/src/components/ui/search.module.csspackages/chronicle/src/components/ui/search.tsxpackages/chronicle/src/lib/page-context.tsxpackages/chronicle/src/lib/source.tspackages/chronicle/src/pages/DocsPage.tsxpackages/chronicle/src/server/api/page.tspackages/chronicle/src/server/entry-client.tsxpackages/chronicle/src/server/entry-server.tsxpackages/chronicle/src/themes/default/Layout.module.csspackages/chronicle/src/themes/default/Layout.tsxpackages/chronicle/src/themes/default/Page.module.csspackages/chronicle/src/themes/default/Page.tsxpackages/chronicle/src/themes/default/Toc.module.csspackages/chronicle/src/themes/default/Toc.tsxpackages/chronicle/src/types/content.ts
💤 Files with no reviewable changes (1)
- packages/chronicle/src/components/ui/search.module.css
✅ Files skipped from review due to trivial changes (2)
- docs/chronicle.yaml
- package.json
🚧 Files skipped from review as they are similar to previous changes (8)
- packages/chronicle/src/pages/DocsPage.tsx
- packages/chronicle/src/server/entry-client.tsx
- packages/chronicle/src/server/api/page.ts
- packages/chronicle/src/themes/default/Page.tsx
- packages/chronicle/src/lib/source.ts
- packages/chronicle/src/server/entry-server.tsx
- packages/chronicle/src/types/content.ts
- packages/chronicle/src/components/ui/client-theme-switcher.tsx
| function TocContent({ items }: { items: TOCItemType[] }) { | ||
| const activeAnchor = useActiveAnchor(); | ||
|
|
||
| return ( | ||
| <aside className={styles.toc}> | ||
| <Text size={1} weight='medium' className={styles.title}> | ||
| On this page | ||
| </Text> | ||
| <nav className={styles.nav}> | ||
| <aside className={styles.toc} aria-label='Table of contents'> | ||
| <div className={styles.markers}> | ||
| {items.map(item => { | ||
| const id = item.url.replace('#', ''); | ||
| const isActive = activeAnchor === id; | ||
| const isNested = item.depth > 2; | ||
| return ( | ||
| <a | ||
| key={item.url} | ||
| href={item.url} | ||
| className={`${styles.link} ${isActive ? styles.active : ''} ${isNested ? styles.nested : ''}`} | ||
| aria-label={nodeToText(item.title)} | ||
| className={cx(styles.marker, isActive && styles.markerActive)} | ||
| style={{ width: `${markerWidth(item.title)}px` }} | ||
| > | ||
| {item.title} | ||
| <span /> | ||
| </a> | ||
| ); | ||
| })} | ||
| </nav> | ||
| </div> | ||
| <div className={styles.panel} role='presentation'> | ||
| <div className={styles.panelHeader}> | ||
| <Bars3BottomLeftIcon width={16} height={16} /> | ||
| <span className={styles.panelHeaderLabel}>On this page</span> | ||
| </div> | ||
| <nav className={styles.panelList}> | ||
| {items.map(item => { | ||
| const id = item.url.replace('#', ''); | ||
| const isActive = activeAnchor === id; | ||
| const isNested = item.depth > 2; | ||
| return ( | ||
| <a | ||
| key={item.url} | ||
| href={item.url} | ||
| className={cx( | ||
| styles.panelItem, | ||
| isNested && styles.panelItemNested, | ||
| isActive && styles.panelItemActive | ||
| )} | ||
| > | ||
| {nodeToText(item.title)} | ||
| </a> | ||
| ); | ||
| })} | ||
| </nav> | ||
| </div> | ||
| </aside> | ||
| ); |
There was a problem hiding this comment.
Hover-only disclosure hides the panel from keyboard and touch users.
The panel is revealed only via .toc:hover in CSS. Keyboard users tabbing through the marker links never see the text labels (no :focus-within rule), and touch devices have no hover at all, so the TOC effectively becomes an unlabeled set of colored bars for those users. Consider revealing the panel on :focus-within as well, and/or making it tap-to-open on touch.
Suggested CSS addition in Toc.module.css
-.toc:hover .markers {
+.toc:hover .markers,
+.toc:focus-within .markers {
opacity: 0;
pointer-events: none;
}
@@
-.toc:hover .panel {
+.toc:hover .panel,
+.toc:focus-within .panel {
opacity: 1;
pointer-events: auto;
transform: translateY(-50%) translateX(0);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/chronicle/src/themes/default/Toc.tsx` around lines 48 - 97, The TOC
panel is currently only revealed on hover which hides labels from keyboard and
touch users; update the TocContent component and CSS so the panel is also
revealed on focus and can be toggled on touch: add tabIndex={0} to the aside
rendered by TocContent (so it can receive keyboard focus) and implement a simple
toggle state (e.g., isOpen) with a clickable button/indicator that sets
aria-expanded on the toggle and adds an "open" class when active; in
Toc.module.css update the reveal rule from .toc:hover to also match
.toc:focus-within and .toc.open (and ensure focus styles are preserved) so
keyboard users (focus-within) and touch users (toggle adds .open) can see the
panel.
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (1)
packages/chronicle/src/themes/default/Toc.module.css (1)
1-7:⚠️ Potential issue | 🟡 MinorAdd responsive handling for the fixed TOC.
The fixed right-anchored TOC still has no breakpoint or layout-aware positioning, so it can overlap article content on narrower viewports.
Suggested responsive guard
.toc { position: fixed; right: calc(var(--rs-space-5) + var(--rs-space-3)); top: 50%; transform: translateY(-50%); z-index: 10; } + +@media (max-width: 1024px) { + .toc { + display: none; + } +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/chronicle/src/themes/default/Toc.module.css` around lines 1 - 7, The .toc rule in Toc.module.css uses a fixed right-anchored position that can overlap content on narrow viewports; update the stylesheet to add a responsive media query (e.g., max-width: 768px or a site breakpoint) that changes .toc from position: fixed to a layout-friendly state (position: static or position: sticky with transform: none, top: auto, right: auto) or hides it (display: none) and adjusts z-index/margins accordingly so the TOC no longer overlaps article content on small screens; target the .toc selector to implement this breakpoint behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/chronicle/src/themes/default/Layout.module.css`:
- Around line 9-15: The .sidebar rule can shrink in flex layouts and its fixed
100vh can clip content; update the .sidebar declaration to prevent shrinking and
enable internal scrolling by using a non-shrinking flex basis (e.g., add flex: 0
0 262px and/or min-width: 262px) instead of a plain width, and add overflow-y:
auto with max-height: 100vh (keep position: sticky; top: 0) so long nav trees
scroll inside the sidebar; apply the same changes to the other sidebar rule
further down that mirrors this styling.
- Around line 119-127: The .card rule in Layout.module.css uses overflow: clip
which trims floating panels and focus rings; update the .card CSS (card class)
to allow overflow to be visible (replace overflow: clip with overflow: visible)
so TOC hover/focus panels and outlines can extend outside the card boundaries,
keeping the existing flex, border, radius and box-shadow intact; if any child
relies on clipping for layout, instead scope clipping to that specific child
rather than the .card.
- Around line 69-98: The tab row can overflow horizontally and needs scroll
handling: update the .tabs (or .tabBar) styles to enable horizontal scrolling by
adding overflow-x: auto and overflow-y: hidden, enable smooth scrolling on touch
with -webkit-overflow-scrolling: touch, and prevent individual tabs from
shrinking by setting .tab to flex: 0 0 auto; also keep white-space: nowrap on
the container so tabs don’t wrap. This ensures many .tab items remain reachable
via horizontal scroll while preserving the existing layout and spacing.
- Around line 129-138: The .subNav rule combines a fixed height with vertical
padding which can alter its rendered height; update the .subNav selector in
Layout.module.css to include box-sizing: border-box (same as .sidebarHeader) so
the 48px height remains stable and padding is included within that height.
In `@packages/chronicle/src/themes/default/Toc.module.css`:
- Around line 43-62: The .panel styles allow the TOC to grow beyond the
viewport; update the .panel rule (in Toc.module.css) to constrain its height and
enable internal scrolling by adding a max-height computed from the viewport
(e.g., using calc(100vh - <offset>)) and overflow-y: auto so long TOC lists
remain reachable; ensure pointer-events and transition behavior remain unchanged
and apply the same change to the other panel variant referenced in the file
(lines ~88-93).
- Around line 18-22: The CSS currently hides .markers when .toc receives
focus-within which makes keyboard-focused controls invisible; change the
behavior so markers are hidden only when the panel is hovered AND not when a
child has keyboard focus: replace the .toc:focus-within .markers rule with a
selector that targets .toc:not(:focus-within) .markers (or alternatively add an
overriding rule .toc:focus-within .markers { opacity: 1; pointer-events: auto })
so .markers remain visible and interactive for keyboard users; update the same
pattern covering the other occurrences of .toc/.markers in the file.
---
Duplicate comments:
In `@packages/chronicle/src/themes/default/Toc.module.css`:
- Around line 1-7: The .toc rule in Toc.module.css uses a fixed right-anchored
position that can overlap content on narrow viewports; update the stylesheet to
add a responsive media query (e.g., max-width: 768px or a site breakpoint) that
changes .toc from position: fixed to a layout-friendly state (position: static
or position: sticky with transform: none, top: auto, right: auto) or hides it
(display: none) and adjusts z-index/margins accordingly so the TOC no longer
overlaps article content on small screens; target the .toc selector to implement
this breakpoint behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 51759f2d-0465-45d0-a0f6-5d258d5619fa
📒 Files selected for processing (7)
packages/chronicle/src/components/ui/search.tsxpackages/chronicle/src/lib/source.tspackages/chronicle/src/server/App.tsxpackages/chronicle/src/server/entry-client.tsxpackages/chronicle/src/server/entry-server.tsxpackages/chronicle/src/themes/default/Layout.module.csspackages/chronicle/src/themes/default/Toc.module.css
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/chronicle/src/server/entry-client.tsx
- packages/chronicle/src/components/ui/search.tsx
- packages/chronicle/src/server/entry-server.tsx
- packages/chronicle/src/lib/source.ts
| .tabBar { | ||
| display: flex; | ||
| align-items: center; | ||
| height: 48px; | ||
| padding: 0 var(--rs-space-2); | ||
| background: var(--rs-color-background-base-secondary); | ||
| } | ||
|
|
||
| .folder > .sidebarList { | ||
| margin-top: var(--rs-space-2); | ||
| padding-left: var(--rs-space-4); | ||
| .tabs { | ||
| display: flex; | ||
| align-items: center; | ||
| gap: var(--rs-space-3); | ||
| } | ||
|
|
||
| .navButton { | ||
| .tab { | ||
| display: flex; | ||
| align-items: center; | ||
| height: 32px; | ||
| padding: 0 var(--rs-space-4); | ||
| border: 1px solid var(--rs-color-border-base-primary); | ||
| gap: var(--rs-space-2); | ||
| padding: var(--rs-space-2) var(--rs-space-3); | ||
| border: 0.5px solid var(--rs-color-border-base-primary); | ||
| border-radius: var(--rs-radius-2); | ||
| font-size: var(--rs-font-size-small); | ||
| font-size: var(--rs-font-size-mini); | ||
| font-weight: var(--rs-font-weight-medium); | ||
| color: var(--rs-color-foreground-base-primary); | ||
| letter-spacing: var(--rs-letter-spacing-mini); | ||
| line-height: var(--rs-line-height-mini); | ||
| color: var(--rs-color-foreground-base-secondary); | ||
| text-decoration: none; | ||
| white-space: nowrap; | ||
| cursor: pointer; | ||
| } |
There was a problem hiding this comment.
Add horizontal overflow handling for the tab bar.
The tabs are non-wrapping links, so multiple APIs can spill outside the main area and become hard to reach.
Proposed fix
.tabBar {
display: flex;
align-items: center;
height: 48px;
padding: 0 var(--rs-space-2);
background: var(--rs-color-background-base-secondary);
+ min-width: 0;
+ overflow-x: auto;
}
.tabs {
display: flex;
align-items: center;
gap: var(--rs-space-3);
+ min-width: max-content;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .tabBar { | |
| display: flex; | |
| align-items: center; | |
| height: 48px; | |
| padding: 0 var(--rs-space-2); | |
| background: var(--rs-color-background-base-secondary); | |
| } | |
| .folder > .sidebarList { | |
| margin-top: var(--rs-space-2); | |
| padding-left: var(--rs-space-4); | |
| .tabs { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--rs-space-3); | |
| } | |
| .navButton { | |
| .tab { | |
| display: flex; | |
| align-items: center; | |
| height: 32px; | |
| padding: 0 var(--rs-space-4); | |
| border: 1px solid var(--rs-color-border-base-primary); | |
| gap: var(--rs-space-2); | |
| padding: var(--rs-space-2) var(--rs-space-3); | |
| border: 0.5px solid var(--rs-color-border-base-primary); | |
| border-radius: var(--rs-radius-2); | |
| font-size: var(--rs-font-size-small); | |
| font-size: var(--rs-font-size-mini); | |
| font-weight: var(--rs-font-weight-medium); | |
| color: var(--rs-color-foreground-base-primary); | |
| letter-spacing: var(--rs-letter-spacing-mini); | |
| line-height: var(--rs-line-height-mini); | |
| color: var(--rs-color-foreground-base-secondary); | |
| text-decoration: none; | |
| white-space: nowrap; | |
| cursor: pointer; | |
| } | |
| .tabBar { | |
| display: flex; | |
| align-items: center; | |
| height: 48px; | |
| padding: 0 var(--rs-space-2); | |
| background: var(--rs-color-background-base-secondary); | |
| min-width: 0; | |
| overflow-x: auto; | |
| } | |
| .tabs { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--rs-space-3); | |
| min-width: max-content; | |
| } | |
| .tab { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--rs-space-2); | |
| padding: var(--rs-space-2) var(--rs-space-3); | |
| border: 0.5px solid var(--rs-color-border-base-primary); | |
| border-radius: var(--rs-radius-2); | |
| font-size: var(--rs-font-size-mini); | |
| font-weight: var(--rs-font-weight-medium); | |
| letter-spacing: var(--rs-letter-spacing-mini); | |
| line-height: var(--rs-line-height-mini); | |
| color: var(--rs-color-foreground-base-secondary); | |
| text-decoration: none; | |
| white-space: nowrap; | |
| cursor: pointer; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/chronicle/src/themes/default/Layout.module.css` around lines 69 -
98, The tab row can overflow horizontally and needs scroll handling: update the
.tabs (or .tabBar) styles to enable horizontal scrolling by adding overflow-x:
auto and overflow-y: hidden, enable smooth scrolling on touch with
-webkit-overflow-scrolling: touch, and prevent individual tabs from shrinking by
setting .tab to flex: 0 0 auto; also keep white-space: nowrap on the container
so tabs don’t wrap. This ensures many .tab items remain reachable via horizontal
scroll while preserving the existing layout and spacing.
| .subNav { | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| height: 48px; | ||
| padding: var(--rs-space-4) var(--rs-space-7); | ||
| background: var(--rs-color-background-base-primary); | ||
| border-bottom: 0.5px solid var(--rs-color-border-base-primary); | ||
| backdrop-filter: blur(1px); | ||
| } |
There was a problem hiding this comment.
Keep the subnav’s 48px height border-boxed.
This selector combines fixed height with vertical padding; add box-sizing here like .sidebarHeader to keep the rendered height stable.
Proposed fix
.subNav {
display: flex;
align-items: center;
justify-content: space-between;
height: 48px;
+ box-sizing: border-box;
padding: var(--rs-space-4) var(--rs-space-7);
background: var(--rs-color-background-base-primary);
border-bottom: 0.5px solid var(--rs-color-border-base-primary);
backdrop-filter: blur(1px);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .subNav { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| height: 48px; | |
| padding: var(--rs-space-4) var(--rs-space-7); | |
| background: var(--rs-color-background-base-primary); | |
| border-bottom: 0.5px solid var(--rs-color-border-base-primary); | |
| backdrop-filter: blur(1px); | |
| } | |
| .subNav { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| height: 48px; | |
| box-sizing: border-box; | |
| padding: var(--rs-space-4) var(--rs-space-7); | |
| background: var(--rs-color-background-base-primary); | |
| border-bottom: 0.5px solid var(--rs-color-border-base-primary); | |
| backdrop-filter: blur(1px); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/chronicle/src/themes/default/Layout.module.css` around lines 129 -
138, The .subNav rule combines a fixed height with vertical padding which can
alter its rendered height; update the .subNav selector in Layout.module.css to
include box-sizing: border-box (same as .sidebarHeader) so the 48px height
remains stable and padding is included within that height.
| .toc:hover .markers, | ||
| .toc:focus-within .markers { | ||
| opacity: 0; | ||
| pointer-events: none; | ||
| } |
There was a problem hiding this comment.
Don’t hide the focused marker for keyboard users.
With .toc:focus-within .markers { opacity: 0 }, tabbing to a marker makes the focused control invisible before focus moves into the panel.
Suggested keyboard-safe adjustment
-.toc:hover .markers,
-.toc:focus-within .markers {
+.toc:hover .markers {
opacity: 0;
pointer-events: none;
}
.marker {
display: block;
height: 2px;
background: var(--rs-color-border-base-secondary);
border-radius: 1px;
transition:
width 0.15s ease,
background 0.15s ease;
}
+
+.marker:focus-visible {
+ outline: 2px solid var(--rs-color-border-base-emphasis);
+ outline-offset: var(--rs-space-2);
+}Also applies to: 24-40
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/chronicle/src/themes/default/Toc.module.css` around lines 18 - 22,
The CSS currently hides .markers when .toc receives focus-within which makes
keyboard-focused controls invisible; change the behavior so markers are hidden
only when the panel is hovered AND not when a child has keyboard focus: replace
the .toc:focus-within .markers rule with a selector that targets
.toc:not(:focus-within) .markers (or alternatively add an overriding rule
.toc:focus-within .markers { opacity: 1; pointer-events: auto }) so .markers
remain visible and interactive for keyboard users; update the same pattern
covering the other occurrences of .toc/.markers in the file.
| .panel { | ||
| position: absolute; | ||
| top: 50%; | ||
| right: 0; | ||
| transform: translateY(-50%) translateX(8px); | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: var(--rs-space-2); | ||
| min-width: 200px; | ||
| padding: var(--rs-space-3) 0; | ||
| background: var(--rs-color-background-base-primary); | ||
| border: 0.5px solid var(--rs-color-border-base-primary); | ||
| border-radius: var(--rs-radius-4); | ||
| box-shadow: var(--rs-shadow-soft); | ||
| opacity: 0; | ||
| pointer-events: none; | ||
| transition: | ||
| opacity 150ms ease, | ||
| transform 150ms ease; | ||
| } |
There was a problem hiding this comment.
Constrain long TOC panels to the viewport.
The centered fixed panel can grow past the viewport for pages with many headings, making some links unreachable. Add a max-height and scroll the list.
Suggested overflow handling
.panel {
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%) translateX(8px);
display: flex;
flex-direction: column;
gap: var(--rs-space-2);
min-width: 200px;
+ max-height: calc(100vh - (2 * var(--rs-space-5)));
padding: var(--rs-space-3) 0;
background: var(--rs-color-background-base-primary);
border: 0.5px solid var(--rs-color-border-base-primary);
border-radius: var(--rs-radius-4);
box-shadow: var(--rs-shadow-soft);
+ overflow: hidden;
opacity: 0;
pointer-events: none;
transition:
opacity 150ms ease,
transform 150ms ease;
}
.panelList {
display: flex;
flex-direction: column;
gap: var(--rs-space-1);
padding-left: var(--rs-space-8);
+ min-height: 0;
+ overflow-y: auto;
}Also applies to: 88-93
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/chronicle/src/themes/default/Toc.module.css` around lines 43 - 62,
The .panel styles allow the TOC to grow beyond the viewport; update the .panel
rule (in Toc.module.css) to constrain its height and enable internal scrolling
by adding a max-height computed from the viewport (e.g., using calc(100vh -
<offset>)) and overflow-y: auto so long TOC lists remain reachable; ensure
pointer-events and transition behavior remain unchanged and apply the same
change to the other panel variant referenced in the file (lines ~88-93).
Prepare Page type for backend-provided prev/next links used by the docs sub-navbar. Adds PageNavLink/PageNav types, extends Page via extends PageNav, unifies PageData with Page in page context, and threads null placeholders through SSR + hydration until backend computes real values. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nitro@3.0.260311-beta lists std-env in devDependencies but its runtime imports it. Node ESM resolution from nitro's isolated cache path fails without std-env at top-level node_modules. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the top Navbar; move navigation into a per-section tab bar alongside the sidebar. Resize sidebar to 262px, swap button-based nav links for pill-style tabs using mini typography tokens. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add getPageNav helper that flattens the page tree and returns the adjacent PageNavLinks for a slug. Both the /api/page handler and SSR pageData now populate prev/next so the client can render navigation without re-flattening the tree. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Default theme now renders the main content inside a rounded, bordered card with a sub-navbar at the top containing prev/next IconButtons driven by the server-provided page nav, followed by breadcrumbs. Breadcrumbs move out of Page.tsx since the Layout owns the chrome. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add sidebar top navbar with placeholder logo, icon-only search, and IconButton-based theme switcher using resolvedTheme so system mode lands on the correct icon. - Simplify Search component to IconButton only, drop cmdk kbd trigger. - Wrap content in a bordered card, remove TOC from Page, align heading + paragraph typography with Aurora tokens. - Secondary bg on sidebar + card wrapper, primary bg on content area. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TOC renders as short horizontal markers floating right-center, each width scaled from the heading text length (base + per char, clamped). Hovering the aside fades the markers out and reveals a panel with the full heading list, active state highlighted. Shadows switch to the Apsara --rs-shadow-soft token. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- page-context: coerce prev/next from API response to null so the state always matches the Page type instead of carrying undefined. - source.getPageNav: guard PageNavLink.title to string-only page names so ReactNode names don't stringify to "[object Object]". - Layout tabs: compute per-entry active state from api.basePath instead of the shared isApiRoute boolean, so only the matching API tab highlights when multiple APIs are configured. - Layout: skip breadcrumbs on API routes since the docs tree doesn't include API paths. - Search: add title="Search (⌘K)" so the keyboard shortcut stays discoverable after the kbd hint was removed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- search shortcut tooltip: platform-neutral "Ctrl/⌘K" - TOC: mirror :hover with :focus-within for keyboard users - getPageNav: derive PageNavLink.title from URL when page name is non-string Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- getPageNav: accept pre-loaded tree to avoid redundant tree rebuild - entry-server: skip getPageNav when page is null; share tree with nav - entry-client: nullable frontmatter/relativePath/originalPath in EmbeddedData; narrow at usage Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SidebarNode recurses to depth 1 with data-depth attr on section - Depth 0: non-collapsible section header - Depth 1: collapsible sub-group with right-aligned chevron - Grandchildren folders (depth > 1) ignored - Hide sidebar scrollbar - Style navGroup header/trigger/label/chevron via apsara classNames slots Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop tabBar (ContentDirButtons + API tabs) and subNav card-chrome - Sidebar header: logo + search + theme only (version switcher moved out) - Sidebar footer: holds VersionSwitcher, hidden when no versions - Sidebar bg uses --rs-color-background-base-secondary - Card: border-left only, no border-radius - cardWrapper: drop horizontal padding - TOC: drop extra right offset Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Render content-dir entries and API entries as Sidebar.Item cells at the top of Sidebar.Main, above the page tree - Hidden when total (content + API) count is ≤ 1 - Drop Apsara's default header margin-bottom Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Top content links: tertiary text by default, primary when active, transparent bg
- Nav group label uses secondary for section headers, primary for sub-groups
- Nav group: zero out default margin-top, apply space-7 only to top-level (data-depth=0)
- Zero out gap on Sidebar.Main and nav-group-items
- Breadcrumbs: use render={<RouterLink>} so SPA navigation works (not-current items)
- Wrap top content/API links in .topLinks div with bottom margin
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3ab607a to
4e9e45e
Compare
- New SidebarLogo component reads config.logo.light/dark - Falls back to BookOpenIcon when no logo is configured - Picks src based on resolvedTheme from Apsara useTheme() Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Schema accepts icon string on content entries and api entries - Icon can be a URL or an inline SVG string (uses currentColor) - renderConfigIcon helper picks <img> for URL or inlines SVG - Fallback to DocumentTextIcon / CodeBracketSquareIcon when absent Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop llms config section; routes serve unconditionally - Remove llms.enabled gating from .md slug route and llms.txt routes - Clean up llms: enabled: true from docs and versioned example yamls Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove footerSchema + FooterConfig from config schema - Delete Footer component and CSS - Remove Footer usage from paper theme Layout - Clean footer: sections from docs and basic example yamls Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Head accepts optional markdownHref; DocsPage passes `/${slug}.md` so
crawlers and AI assistants can discover the raw markdown source.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds OpenInAI menu in the subNav with four actions: - Copy as MD (fetches the page's .md and writes to clipboard) - View MD (opens the raw .md in a new tab) - Open in ChatGPT (chatgpt.com/?q=Read <mdUrl>) - Open in Claude (claude.ai/new?q=Read <mdUrl>) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Bump @raystack/apsara to 1.0.0-rc.4 - Drop manual Dialog wrapper; use Command.Dialog + Command.DialogContent - Command.List -> Command.Content - Command.Group heading -> <Command.Label> - Item onSelect -> onClick; Input leadingIcon replaces external icon - Style dialogContent (width/radius/top) to position at 20% from top Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace four 48px usages (sidebar header, sidebar footer, subNav, etc) with var(--navbar-height) defined on .layout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inline SVG icons using currentColor so they inherit the text color. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Layout: tighten isApiRoute match (exact /apis or /apis/ prefix) so breadcrumb suppression doesn't catch unrelated paths - Sidebar: flex: 0 0 262px + flex-column so it doesn't shrink; Main gets overflow-y: auto + min-height: 0 for long trees - Card: overflow visible so floating TOC + focus rings aren't clipped - Toc markers: aria-hidden + tabIndex=-1 so the panel nav is the single accessible entry - Toc: hide below 900px viewport via media query Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Figma: https://www.figma.com/design/HYWvap3F9zHURz4MwNHb2d/Aurora-Docs
Summary
Layout + sidebar
Sidebar.Group, nested folder as collapsible with right-aligned chevronConfig + content features
config.logo.light/config.logo.darkdrive sidebar logo, book-open fallbackiconper content dir and API entry — URL or inline SVG;dangerouslySetInnerHTMLfor SVG socurrentColorworksconfig.llms.enabled— raw.mdroute andllms.txtalways onfooterconfig + Footer component<link rel="alternate" type="text/markdown">injected on docs pages for AI/crawler discoveryOpen in AI
Breadcrumbs + search
render={<RouterLink>}so SPA navigation works@raystack/apsarato 1.0.0-rc.4 and migrate Search toCommand.Dialog+Command.DialogContent+Command.Content+Command.LabelTODO
Tracked in
TODO.mdat repo root.Test plan
bun run dev:docs— sidebar groups expand, active state correct, nested folders show chevron right, rotate down when openbun run build:docs && bun run start:docs— /docs, /apis, 404 render; /docs/*.md and /llms.txt serve<link rel="alternate" type="text/markdown" href="/slug.md" title="{Page} (Markdown)">🤖 Generated with Claude Code