Skip to content

Add a dark theme, a two level outline and a writing guide - #204

Merged
hwbrzzl merged 11 commits into
masterfrom
kkumar-gcc/dark-theme
Sep 25, 2026
Merged

hwbrzzl merged 11 commits into
masterfrom
kkumar-gcc/dark-theme

Conversation

@krishankumar01

@krishankumar01 krishankumar01 commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

Dark theme

  • Adds a dark theme that follows the visitor's system setting, with a toggle in the nav bar that remembers their choice
  • Every colour already went through the theme tokens, so dark is a second set of token values, with a matching dark palette for code highlighting
  • Code blocks sit darker than the page in dark mode, the same way they sit darker than white in light mode
  • Warning and error code lines, callout code blocks, keys and badges now use the theme tokens in both themes

Outline

  • The outline now lists ## and ### headings, the way the Laravel docs do. It works at every screen width, including the "On this page" dropdown on phones
  • This replaces the Methods list, which only read headings that look like method names, so most pages had none, and which only existed on wide screens. The component, its detection rules and its strings are removed
  • The outline title stays pinned while a long list scrolls
  • The active marker is no longer cropped by its link, and the "On this page" label on narrower screens lines up with the content

Homepage

  • Grid markers follow the real column count at every screen width
  • On phones the tabs stay on one row that scrolls, the pane label stays on one line, and the rule between the join cells reaches both screen edges

Fixes

  • The nav bar's bottom rule now reaches the sidebar border. The Brand component's global styles had leaked a margin onto the nav's brand cell, so its styles are scoped
  • Tailwind no longer generates its outline utility, which matched a VitePress class and drew a border inside the "On this page" dropdown
  • Adds Telemetry to the Laravel comparison

Writing guide

  • Adds prologue/writing-docs in English and Chinese: how to write a good page, and every theme feature shown as source and as rendered. It is not listed in the sidebar or search
  • A fence opened as md demo ```` shows its source and renders it from the same text, so each example is written once
  • README.md and AGENTS.md point to that one guide. Both are excluded from the site build, which removes the accidental /README.html page

Checked

  • pnpm docs:build passes in CI
  • Checked by hand in a browser, in both themes: the homepage, the writing guide, the 404 page and a sample of docs pages, for page errors, sideways scroll and the contrast of text against its ground

- dark token set in goravel.css, following the system setting, with a toggle in the nav bar
- dark palette for code highlighting, and code grounds darker than the page
- warning and error code lines, callout code, keys and badges use the theme tokens
- homepage grid markers follow the real column count
- file name headers also accept dotfiles such as .env
- read methods named in a section's text and called in its code, method tables that link to anchors, and one-word headings the section's code calls
- a methods attribute on a heading lists exactly those names and always wins
- the outline and the Methods list scroll separately, so their titles and the filter stay visible
- prologue/writing-docs in English and Chinese, not listed in the sidebar or search
- a fence opened as md demo shows its source and renders it, so an example is written once
- CONTRIBUTING.md, AGENTS.md and CLAUDE.md point to the guide, and are kept out of the site build with README.md
- tabs stay on one row that scrolls, and keep the active tab in view
- the pane label stays on one line with the file path under it on phones
- the rule between the two rows of join cells reaches both screen edges
- the outline lists ## and ### headings, the way the Laravel docs do, at every screen width
- remove the method index component, its detection rules, its strings and the methods heading attribute
- the outline title stays pinned while a long list scrolls
- the writing guide no longer teaches the method index
@krishankumar01 krishankumar01 changed the title Add a dark theme, a wider method index and a writing guide Add a dark theme, a two level outline and a writing guide Sep 20, 2026
- the active outline marker sits inside its link, so the link no longer crops it
- remove the spacing left under the outline by the old Methods list
- scope the Brand styles, which leaked an 8px margin onto the nav brand and kept the nav rule from reaching the sidebar border
- align the On this page label with the content
- stop Tailwind generating its outline utility, which drew a border on the dropdown list
Comment thread CLAUDE.md Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Claude has supported the AGENTS.md file, so it's unnecessary.

Comment thread CONTRIBUTING.md Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This file can be merged into README.

@goravel-coder

goravel-coder commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

🤖 Automated review

This is an AI-generated code review. Please double-check each finding before acting.

Summary

This PR adds a system-following dark theme with a nav toggle, replaces the Methods list with a two-level ##/### outline, reworks the homepage responsive grid/tabs, and adds a bilingual writing guide driven by a new ````md demofence. The change is well-executed and the build passes, but the demo fence's rendered headings leak into the very outline the PR introduces, several new raw colours bypass the token rule the PR itself adds, and the Shiki light palette now drifts from--g-grey`.

Verdict

  • Must Fix: 0 · Should Fix: 6 · Nits: 13

Findings

Must Fix

None.

Should Fix

  1. .vitepress/config/shared.ts:56 Demo output injects real headings into the page outline — renderDemos concatenates md.render(content, { ...env }) straight into .VPDoc. VitePress's client getHeaders() (theme-default/composables/outline.js:17) scans .VPDoc for h1–h6 with ids and only skips nodes carrying ignore-header, so with the new outline.level: [2, 3] the guide's own demo heading (en/prologue/writing-docs.md:44, zh_CN/prologue/writing-docs.md:44) renders as h3#storing-items-for-a-limited-time and shows up in the aside and the mobile dropdown as a child of "Writing A Section"; the { ...env } copy does not isolate it because the outline is read from the DOM (verified in the built .vitepress/dist/prologue/writing-docs.html). Suggestion: wrap the rendered result in <div class="ignore-header">…</div>.
  2. .vitepress/config/goravel-code.ts:10 Light Shiki palette drifts from the theme tokens — master's GREY was #68747d, matching --g-grey; this PR changes LIGHT.grey to #636f78 while --g-grey stays #68747d, so comments and strings in code blocks use a different grey from the rest of the site, and every future token edit must be mirrored in two places — exactly the rule the PR adds at AGENTS.md:9. Suggestion: derive the Shiki palette from one shared source (or the --g-* values).
  3. .vitepress/theme/home/home.css:14 Dark home grey written as a raw colour — .dark .g-home { --g-grey: #93a2ad; } hardcodes a colour and overrides the central token locally; it differs from the dark --g-grey (#8a99a5) and joins the pre-existing #5b6770 override on line 9, giving the homepage a third grey outside the token set. Suggestion: add a named token in goravel.css and reference it in both themes.
  4. .vitepress/theme/home/GoravelMark.vue:57 Mark ground is hardcoded and its ghost outline is not themed — ground duplicates --g-white as RGB literals ([13, 20, 27] / [255, 255, 255]), so a token change silently mis-tints the mark, and paint() (:72) keeps stroke: rgba(104, 116, 125, …) fixed while the fill now follows isDark, so the ghost outline barely shows on the dark ground. Suggestion: read the token (or --g-construct) so both track the theme.
  5. .vitepress/theme/goravel.css:14 The "no text below 4.5:1" claim is unenforced and false for two light-theme pairs — no contrast test or CI check exists, and --g-on-accent (#ffffff) on --g-cyan (#009fe8) measures 2.95:1 (404 CTA, components/NotFound.vue:74) while --g-grey (#68747d) on --g-hover (#eef2f5) measures 4.26:1 (search hover, components/NavBar.vue:237, :263). Both values predate the PR, but the new --g-on-accent token and the PR's Checked list make them relevant. Suggestion: darken the light values, or scope the claim.
  6. en/prologue/writing-docs.md:15 The guide relies on the md demo fence without documenting it — the page promises examples are "written once" and uses ````md demothroughout, but no section tells a writer the fence exists, when to use it, or that its body is rendered a second time (and can reach the outline). Suggestion: add a "Demos" subsection beside "Code Blocks" (alsozh_CN/prologue/writing-docs.md:15`).

Nits

  1. .vitepress/config/en.ts:92 h3 in the outline multiplies aside DOM and per-scroll work — outline.level: [2, 3] (also .vitepress/config/zh_CN.ts:77) takes pages such as en/digging-deeper/strings.md from 2 to ~92 entries, and VitePress's useActiveAnchor registers a non-passive, 100 ms-throttled window scroll listener that measures every header each tick. Suggestion: keep the global default at 2 and opt in via frontmatter on the pages that need it.
  2. .vitepress/config/goravel-code.ts:43 Dual light/dark Shiki theme roughly doubles the per-token inline style payload — markdown.theme is now { light, dark }, so every token <span> carries both --shiki-light and --shiki-dark instead of one color. Suggestion: measure the gzipped build delta; if it is material, keep one theme and remap token colours for .dark via CSS.
  3. .vitepress/theme/styles.css:5 @source not inline('outline') is cryptic and only verifiable by inspecting output — the fix works (no .outline{} in the built assets/style.*.css), but unlike the neighbouring @plugin/@source lines it has no comment, and CI only runs docs:build, so a Tailwind regression would ship silently. Suggestion: add a one-line comment and/or assert the utility's absence over dist.
  4. .vitepress/config/shared.ts:52 renderDemos is new pure logic with no coverage — the repo has no test script or runner, but shared.markdown.config(md) is exported and cheap to drive. Suggestion: one test that renders a ````md demo` fence and asserts source + rendered output, plus a plain fence asserting source only.
  5. en/prologue/writing-docs.md:2 search: false is inert with the Algolia provider — the site uses provider: 'algolia' (.vitepress/config/shared.ts:140) and the search frontmatter is only read by the local-search plugin, so exclusion rests entirely on the noindex meta on line 6 (also zh_CN/prologue/writing-docs.md:2). Suggestion: drop the line, or comment that noindex is the mechanism.
  6. en/prologue/writing-docs.md:227 The bilingual invariant is asserted but unverified — heading levels and order currently line up (checked), but this 248-line pair must stay in sync and nothing enforces it. Suggestion: a cheap test comparing heading levels/order and fenced-block languages across en/ and zh_CN/.
  7. en/prologue/writing-docs.md:237 British "colour" — the rest of the docs use American English ("color values", "colorized"). Suggestion: use "color".
  8. en/prologue/writing-docs.md:248 "no page errors or sideways scroll" has no automated backing — docs:build cannot catch client runtime errors or horizontal overflow. Suggestion: a Playwright smoke pass (load a few pages in both themes; fail on console errors and scrollWidth > clientWidth), or label the claim as manual.
  9. zh_CN/prologue/writing-docs.md:91 警告: translates "Warning", not one of the English terms — English lists Note:, Tip: or Attention:, while the Chinese lists 注意:、提示: 或 警告:. Suggestion: align the Chinese with the English terms.
  10. .vitepress/theme/components/NavBar.vue:70 The theme toggle repaints the whole document — @click="isDark = !isDark" flips .dark on <html>, forcing a style recalculation against the large --g-* token block for every element, and the per-line background-color/color transition on .g-code .ln (home/home.css:311) animates that repaint for hundreds of lines. Suggestion: check toggle jank on the longest pages and drop the per-line transition if it is noticeable.
  11. .vitepress/theme/components/NavBar.vue:184 Hardcoded shadow colours — rgba(16, 24, 32, …) (also components/SelectMenu.vue:71, shell.css:360, :368) are the only non-token colours left in the theme; on the new dark ground they read as no elevation. Suggestion: tokenise as --g-shadow.
  12. .vitepress/theme/home/HomeLaravel.vue:30 The tab auto-scroll watcher mixes measurement with tour state — await nextTick() then tabs.value!.scrollLeft = tab.offsetLeft - 16 forces a synchronous layout on every ticker tick, uses a non-null assertion, and couples tour logic to layout. Suggestion: extract a small composable and cache offsets on mount/resize, or use scrollIntoView.
  13. AGENTS.md:9 The new "never write a colour" rule is not yet true of the code it ships with — #ff2d20 is still raw in components/Brand.vue:25 and home/HomeLaravel.vue:219 (pre-existing), and this PR adds two more raw colours (home.css:14, GoravelMark.vue:57). Suggestion: extract --g-laravel / --g-grey-home tokens, or soften the wording so the rule matches the codebase.

Automated Checks

  • vitepress build on the PR worktree — passed; dist inspected to confirm the demo heading/anchor and the absence of Tailwind's .outline utility
  • No test script, vitest/jest/playwright config, or test files present in the repo

- headings rendered by a demo block are kept out of the outline
- light grey token matches the code palette, and the homepage grey is a token in both themes
- the 404 button uses the darker cyan, so its text clears 4.5:1 in light mode
- the mark reads its ground and outline colours from the theme tokens
- shadows and the Laravel red are tokens
- the guide documents the demo block, and its search flag is dropped since Algolia ignores it
- Claude reads AGENTS.md, so CLAUDE.md is not needed
- the README carries the pointer to the writing guide
@goravel goravel deleted a comment from goravel-coder Sep 23, 2026
@hwbrzzl

hwbrzzl commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Hey @krishankumar01 I'm optimizing the auto review structure and flows, welcome any suggestions.

@hwbrzzl

hwbrzzl commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

I'm thinking of building a goravel-coder github app to review prs, implement features remotely.

@goravel goravel deleted a comment from goravel-coder Sep 25, 2026
@goravel-coder

goravel-coder commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

🤖 Automated review

This is an AI-generated code review. Please double-check each finding before acting. You don't need to address every issue if they are inaccurate, but please point them out if any exist.

Summary

This round reviews the dark-theme/outline/writing-guide branch at b266ad51f, after the author's "address review of the dark theme" commit. The prior round's six Should-Fix items and six of its Nits are fixed (verified against the code, not the commit message); what remains is the demo-fence plumbing, one narrow srcExclude pattern, and the mark's colour fallbacks.

Verdict

  • [NEW] Must Fix: 0 · Should Fix: 2 · Nits: 8
  • ROUND 1 Must Fix: 0 · Should Fix: 0 · Nits: 7 · Resolved: 12

Findings

Should Fix

  1. .vitepress/config/shared.ts:58 Demo links are checked as real page links
    Problem: The nested md.render for a md demo block shares the page's env.links and env.linkLines arrays.
    Impact: A link written only to illustrate syntax can fail pnpm docs:build, and the reported line points into the demo, not the page.
    Fix: Give the nested render its own link arrays.

    // before
    return source + md.render(tokens[idx].content, { ...env }).replace(/<h([1-6])\b/g, '<h$1 class="ignore-header"')
    // after
    return source + md.render(tokens[idx].content, { ...env, links: [], linkLines: [] }).replace(/<h([1-6])\b/g, '<h$1 class="ignore-header"')
  2. .vitepress/config/shared.ts:80 Nested README files still get built
    Problem: srcExclude: ['README.md', 'AGENTS.md'] matches only the root files, so zh_CN/the-basics/README.md is still built.
    Impact: The site keeps a stray /zh_CN/the-basics/README.html page that the exclusion looks like it prevents.
    Fix: Use **/ globs so the rule covers every depth.

    // before
    srcExclude: ['README.md', 'AGENTS.md'],
    // after
    srcExclude: ['**/README.md', '**/AGENTS.md'],

Nits

  1. .vitepress/config/shared.ts:58 Heading class added twice
    Problem: The rewrite prepends class="ignore-header" to every heading without checking whether one already exists.
    Impact: A demo heading written as raw HTML with its own class gets two class attributes, so the HTML is invalid and the author's class is dropped.
    Fix: Only add the class when the tag has none.

    // before
    .replace(/<h([1-6])\b/g, '<h$1 class="ignore-header"')
    // after
    .replace(/<h([1-6])(?![^>]*\bclass=)/g, '<h$1 class="ignore-header"')
  2. .vitepress/config/shared.ts:58 Demo outline fix relies on an internal class (declined — see Resolution)
    Problem: The pipeline hardcodes VitePress's internal ignore-header class, which is not part of its documented config surface.
    Impact: If VitePress renames that class, every demo heading silently reappears in the outline with no build error.
    Fix: Name the class once and add a build check that demo headings are absent from the outline.

    // before
    .replace(/<h([1-6])\b/g, '<h$1 class="ignore-header"')
    // after
    const SKIP_HEADING_CLASS = 'ignore-header'
    .replace(/<h([1-6])\b/g, `<h$1 class="${SKIP_HEADING_CLASS}"`)
  3. .vitepress/config/goravel-code.ts:10 Syntax palette copies theme token values
    Problem: LIGHT and DARK repeat the hex values of --g-ink, --g-grey, --g-cyan-text and --g-red-text with nothing tying them together.
    Impact: A later token edit leaves the code highlighting out of step with the page, which is what happened to the grey before this PR.
    Fix: Feed both the Shiki themes and the CSS custom properties from one palette, or comment the token each value mirrors.

    // before
    const LIGHT: Palette = { ink: '#101820', grey: '#636f78', cyan: '#0074ae', red: '#b02b2b' }
    // after
    // mirrors --g-ink, --g-grey, --g-cyan-text, --g-red-text
    const LIGHT: Palette = { ink: '#101820', grey: '#636f78', cyan: '#0074ae', red: '#b02b2b' }
  4. .vitepress/theme/home/GoravelMark.vue:58 Mark seeds literal colours, one now stale
    Problem: ground and outline start as literal RGB triples, and outline still starts from [104, 116, 125] (#68747d), the grey this PR replaced.
    Impact: The server-rendered mark draws ghost strokes in the old grey until the script runs, and the literals bypass the token rule in AGENTS.md.
    Fix: Seed the fallbacks from the current token values, or read them before first paint.

    // before
    const outline = ref([104, 116, 125])
    // after
    const outline = ref([99, 111, 120]) // #636f78, --g-grey
  5. .vitepress/theme/home/GoravelMark.vue:61 Mark reads the root grey, not the home grey
    Problem: read() reads --g-grey from document.documentElement, but .g-home redefines --g-grey to --g-grey-strong.
    Impact: The mark's ghost outline uses #636f78 while the surrounding home page uses #5b6770, so the two greys differ.
    Fix: Read the computed style from the home container so its token override applies.

    // before
    const style = getComputedStyle(document.documentElement)
    // after
    const style = getComputedStyle(document.querySelector('.g-home') ?? document.documentElement)
  6. .vitepress/config/shared.ts:45 Demo loses a leading file-path line
    Problem: liftFileNames runs on the md demo fence before renderDemos, so a demo whose first line is a path comment has that line stripped from both the shown source and the render.
    Impact: A demo that teaches the file-name header silently drops the very line it demonstrates.
    Fix: Skip md demo fences in liftFileNames.

    // before
    if (!path || /\{[\d,-]+\}/.test(token.info)) return fence(tokens, idx, options, env, self)
    // after
    if (/^md\s+demo\b/.test(token.info.trim()) || !path || /\{[\d,-]+\}/.test(token.info)) return fence(tokens, idx, options, env, self)
  7. en/prologue/writing-docs.md:22 British spelling in a US-English guide
    Problem: The guide writes "Organise", while the rest of the docs use US spelling.
    Impact: The page that sets the writing style introduces the inconsistency it should prevent.
    Fix: Change the word to "Organize".

  8. .vitepress/theme/home/HomeLaravel.vue:29 Tab scroll depends on child order
    Problem: The tour finds the active tab by indexing tabs.value.children, tying tour state to the DOM order of the strip.
    Impact: Adding any other element inside .g-tabs scrolls to the wrong tab.
    Fix: Keep template refs to the buttons and index those instead.

    // before
    const tab = tabs.value?.children[at.value] as HTMLElement | undefined
    // after
    const tab = tabButtons.value[at.value]

Round reconciliation notes: Round 1's six Should-Fix items are resolved — verified in the current tree, not just the "address review" commit message: demo headings now carry ignore-header (VitePress's buildTree drops them and their children); --g-grey now equals LIGHT.grey; .g-home uses var(--g-grey-strong); the mark reads --g-white/--g-grey; the 404 button uses --g-cyan-text (≈5.1:1) and the search-hover pair now measures ≈4.6:1; the guide gained a "Demos" section. Resolved Nits: @source not inline('outline') now has a comment, search: false dropped, "colour"→"color", 警告: aligns with Warning:, shadows and Laravel red are tokens.

Round 1 items: addressed or declined — see Resolution below.

Comment context (step 4): The two inline review comments (CLAUDE.md:1, CONTRIBUTING.md:1) and the two issue comments do not rebut any current finding, so nothing was withdrawn. No prior comment needed ticking — all 12 resolved findings were already checked.

Resolution

Addressed in 9ad01f4c8 on kkumar-gcc/dark-theme; pnpm docs:build passes.

Fixed (9 of 10): both Should Fix items and Nits 1, 3, 4, 5, 6, 7, 8.

  • a md demo link no longer joins the page dead-link check (nested render uses links: [])
  • srcExclude uses **/README.md and **/AGENTS.md, so zh_CN/the-basics/README.html is no longer built
  • the heading rewrite keeps an existing class attribute
  • the Shiki palette comment names the tokens it mirrors
  • the mark seeds --g-grey-strong and reads --g-grey from the .g-home scope
  • liftFileNames skips md demo fences
  • "Organise" → "Organize"
  • the tab strip is scrolled via template refs, without moving the page

Declined (1): Nit 2 — ignore-header is used once and is VitePress's own DOM contract (outline.js reads it), so a local constant adds indirection without removing duplication.

Round 1 items: the tab-watcher layout read is addressed by the strip-only scroll above. The other six are declined: the two-level outline is this PR's stated feature; the dual Shiki theme is how dark code highlighting works; a test runner is project infrastructure beyond this PR's scope; the browser-check wording lives only in the PR description; the code-line colour transition animates the tour's highlight and is intentional.

- a link inside a `md demo` no longer joins the page's dead-link check
- srcExclude matches nested README/AGENTS files
- keep a demo heading's own class attribute
- note that the Shiki palette mirrors the theme tokens
- the mark reads its grey from the home scope and seeds the matching fallback
- liftFileNames skips demo fences
- US spelling in the writing guide
- scroll the tab strip only, without moving the page
@hwbrzzl
hwbrzzl merged commit 03937ee into master Sep 25, 2026
3 checks passed
@hwbrzzl
hwbrzzl deleted the kkumar-gcc/dark-theme branch September 25, 2026 11:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants