Skip to content

Redesign the homepage and docs theme - #202

Merged
krishankumar01 merged 32 commits into
masterfrom
kkumar-gcc/home-page-redesign
Sep 20, 2026
Merged

krishankumar01 merged 32 commits into
masterfrom
kkumar-gcc/home-page-redesign

Conversation

@krishankumar01

@krishankumar01 krishankumar01 commented Sep 17, 2026 •

Copy link
Copy Markdown
Member

What

A redesign of the homepage and the docs theme.

  • New homepage: follow one request through the framework, compare Laravel and Goravel code, explore the five layers, and start from Goravel Lite.
  • Docs: new typography, code blocks, callouts, tables, sidebar, outline and search.
  • A new nav bar with version and language menus. Switching language keeps you on the same page.
  • A breadcrumb above each article and a method index on reference pages.
  • Code blocks show a file name when their first line is a path comment, like // config/app.go.
  • Markdown now supports footnotes, task lists, sub/sup and definition lists.
  • A new 404 page.

Notes for review

  • Dark mode is turned off, since the redesign only has a light theme.
  • The homepage cube is a static SVG in GoravelMark.vue. To change the drawing, edit its paths.
  • 186 markdown files changed: notes written as blockquotes became callouts, and unused [[toc]] lines were removed.
  • The Chinese and Uzbek homepage text needs a check from native speakers.

Testing

Checked in Chrome and Firefox at desktop, tablet and phone widths, in English, Chinese and Uzbek. pnpm docs:build passes.

Rebuild the site around the Goravel mark. The mark is treated as real
geometry rather than a logo: the lattice it is cut from, its five
pieces, and the path a request takes through it are derived from
public/logo.svg and shared by every drawing on the site.

Homepage (layout: goravel-home)

The stock VitePress home layout showed an adjective headline and six
emoji feature cards with no code on the page. It is replaced by seven
full height sections, each carrying one idea and something the reader
can work:

- Hero: the same route written in Laravel PHP and in Goravel Go, so the
  reason a Laravel developer would switch is visible before any scroll
- Follow one request: GET /tasks stop by stop, with the file and the
  lines that run at each stop
- Laravel's structure, written in Go: eight concepts in both languages,
  with the matching lines lit together in each file
- Five layers, thirty facades: the mark pulled apart, a piece at a time
- Start with the core: Goravel Lite filling in from 4 of 30 facades
- Open source: stars, contributors and the community links the page has
  always carried

Sections advance themselves while on screen, stop the moment the reader
picks a tab, and hold still under prefers-reduced-motion.

Documentation shell

- Three column grid with one rule per column edge: the sidebar column
  runs unbroken from the top of the screen, and everything to its right
  is capped by a single hairline under the header
- Rules inside a column run from that column's rule to the edge of the
  screen, so no rule stops in the middle of the page
- Header is one row on the page's own grid: wordmark, links, search,
  language and project links, with no dividers of its own
- Search is a first class control, matching the outlined button, and
  its modal is one white sheet
- Sidebar rows carry no rules; section, page and current page are told
  apart by weight and a 2px accent on the column edge
- Outline rail leads with On this page; the method index, built from
  the rendered page, follows it

No markdown content changes. Sidebar groups are collapsed by default
and reordered so Upgrade and Prologue come last.
The comparison carried one line per concept in places, which showed the
naming matches but not that the shape of the code matches. Each concept
now shows enough of the real call to be judged:

- Validation shows the rules map and what happens when it fails
- Queues shows job arguments and the queue it goes on
- Cache shows Remember with the closure that fills it
- ORM adds the limit so the chain reads as a chain
- Scheduling wraps the way it is actually written

Testing is added as a concept, since it is one of the reasons to pick a
framework at all: a feature test hitting /tasks and asserting 200, in
PHPUnit and in Goravel's suite.

Every Go snippet uses the API as documented: facades.Cache().Remember,
facades.Queue().Job().OnQueue().Dispatch, s.Http(s.T()).Get and
response.AssertStatus.

Also ignore design/, which holds the design sources rather than code
that ships with the site.
The homepage speaks in small mono labels marked with the lattice cell
the mark is cut from. The docs used the same mono but not the same
marker, so the two read as separate products.

- The layer tag becomes a label: uppercase, tracked, with a cyan cell
- On this page and Methods take the same cell in construct grey
- The method index drops to 11.5px on an 18px line and gains air above
  it, so it reads as an aid to the outline rather than a second outline

A page now says which layer of the framework it belongs to in the same
voice the homepage used to teach the five layers.
The bar mixed destinations with controls: Quickstart and Translate were
content links, Versions and Video Tutorials were menus, and none of them
described the shape of the documentation. Four destinations now do:

- Docs, the way in: installation, configuration, structure
- Guides, the how to: the basics, digging deeper, database, ORM,
  testing, security, AI
- Framework, the internals: lifecycle, container, providers, facades
- Community, a menu: GitHub, Discord, the video series, the
  contribution guide and how to add a language

Versions leaves the bar because the sidebar already carries a version
control, and Translate leaves it because the locale switcher already
sits beside the search. Each destination sets an activeMatch so the bar
shows where the reader is.

Applied to all three locales.
Three gaps found while reading the page rather than measuring it.

The thirty facades on the homepage were plain text. They are the
quickest way into the reference, so each one now links to the page that
documents it, from Route through to Telemetry.

The Community menu pulled every link into one list. It now carries three
sections laid side by side, each with a label in the shell's voice:
Connect, Learn and Contribute. A menu with sections reads as sections.

The Lite build skipped a layer. Four steps added one piece each and then
Install everything filled in the Application layer along with the other
twenty four facades, so one of the five pieces never arrived on its own.
Adding Validation gives every layer its own moment: core, HTTP, data,
async, application, then the whole set.
The menu was a rounded card floating over the page on a shadow, sitting
on top of the bar's own rule. That is the one thing this design does not
do anywhere else, and no amount of adjusting its padding was going to
fix it, which is what the last few passes were doing.

It is now a panel. Its top edge is the bar's rule. Its columns are
divided by verticals that run the panel's full height, from that rule
down to the panel's own rule, which bleeds the width of the screen. Its
content stands on the same grid as the bar above it, 96px in on the
homepage and 40px in the docs.

Rows carry an icon: a brand mark where the destination has one people
already know, a line drawn at the shell's own weight where it does not.
Both sit back in grey and follow the label to the accent. The theme's
external arrow goes, since the icon already says where a row leads.

The locale switch keeps the small sheet. It is a control with three
options, not a section of the site.
The video series and the written guides were the only two rows still
carrying a favicon fetched from youtube.com and devchalk.com: a third
party request from the navigation, a raster mark that cannot take the
row's colour, and a weight that did not match the icons beside it. Both
are drawn now. The play mark is deliberately generic, because that row
points at YouTube in English and Uzbek and at Bilibili in Chinese.

On the installation page, "Tip" was a level four heading wearing a
callout's clothes. It put an entry in the page outline that is not a
section, and gave the page an anchor of %E2%9C%85-tip. It is a tip
container now, which is what it always was. Three sibling headings lose
the emoji they were carrying, and a horizontal rule that sat between two
headings goes, since the headings already do that work.

Applied to all three locales.
Three headings lost their emoji in the last change. That was my call and
it was the wrong one: an emoji in a heading an author chose is a voice,
not noise, and stripping it is not mine to decide.

The Tip container stays, because that was a different problem: a heading
that was not a section, putting an entry in the outline and giving the
page an anchor of %E2%9C%85-tip.
Typography
- Mona Sans for text and headings, IBM Plex Mono kept for code and labels.
  Width carries the display voice, so the homepage and page titles take a
  wider cut and the docs stay at normal width.
- Body moves to 17px/29px. Measured across 318 rendered lines: median 68
  characters, which is what the measure token always claimed.
- Name a CJK fallback per platform, since Mona Sans covers latin only.

Callouts
- info, tip, warning and danger were two looks for four names. Each now has
  its own ground, rule weight and mark. Severity is the one place the palette
  gives way: warning takes amber, danger red, both confined to a near-white
  tint, a 2px rule and an 8px mark.
- A title an author typed is set as text, not as a type label. The markup
  VitePress emits is identical either way, so the container renderer is
  wrapped to mark the difference.
- details reads as a disclosure: the lattice mark leads, a chevron closes the
  row and turns when it opens.

The shell
- A docs page carries no site navigation, because the sidebar is the
  navigation there. The bar keeps the wordmark, search, version, language and
  the project. The homepage keeps the destinations.
- Goravel / Docs is one lockup, so the word no longer appears in the bar
  twice. Discord and X leave the bar and live in Community only.
- Version and language are the same control built the same way, and the
  version chooser lists versions, not the hostnames they are served from.
- Search sits over the article column and is exactly as wide as the measure.
- The bar is one object across the site: one inset at every page and width.
- Community links move to a docs footer, which the hidden nav would otherwise
  have stranded.

Markdown
- Add footnotes, task lists, sub, sup and definition lists, none of which
  VitePress ships, and style each in the existing vocabulary.
- Enable Shiki's word-highlight transformers, so both the comment and the
  fence syntax work instead of rendering as text.
- Convert 15 blockquotes that were doing a callout's job into typed
  containers, classified by what each does to the reader rather than by the
  word Note, and mirror them into the other two locales where the count
  matches. Two are left for someone who reads the language.
- Drop 180 dead toc directives: the outline carries the headings beside the
  article and the local nav carries them on a phone.

Code blocks
- The line-number gutter had no line-height of its own and inherited the
  body's, so every number drifted four pixels further from its line. Across
  105 blocks the worst drift is now one pixel.
- A block that scrolls inside itself cannot keep a gutter in sync, so the
  upgrade guide drops its numbers and wraps rather than clipping lines.
- Diff blocks get room for their markers.
- Code inside a callout is the same block on a lighter ground, and that
  ground is painted to the band's edges rather than laid out, so the gutter
  and the text keep a normal block's geometry.
- Keep the language label visible on hover.

Elsewhere
- Brand marks in the community menu carry their owner's colour and the video
  row follows its own platform per locale.
- Serve the wordmark from a resampled asset: the original is an indexed PNG
  the browser had to take down elevenfold.
- The primary button was white on cyan at 2.95:1. Ink on the same cyan
  measures 6.07:1.
- Consolidate the stylesheets: no selector is now declared twice, verified by
  comparing 116,348 computed properties across 3,422 elements before and
  after.
- Code inside a callout paints its ground to the band's edges. The block
  clipped its own overflow, which cut the header rule back to the inset box
  while the ground bled past it; the horizontal scroll lives on the pre, so
  the block no longer clips. At narrow widths the theme already bleeds code
  by 24px, so the rule takes no extra offset there.
- The sidebar and the outline each had three active signals at once: a
  colour, a weight and a bar. Each now has one. The page you are on is cyan
  with the lattice mark beside its label; the section you are in goes to ink
  with the same mark.
- The docs footer is one row of the next few places to go, not a second site
  navigation. The homepage keeps the fuller footer.
- A heading sits close to the paragraph it introduces and far from the
  section before it, so long pages read as chapters while scrolling.
- Small technical labels move from 11px to 12px.
- Rewrite goravel.css and shell.css so every element is styled once.
  Tables, the pager, callouts and code blocks were each declared up to five
  times. A band now bleeds through two variables instead of a copy of every
  rule for each width.
- Replace the VitePress nav bar with a component of our own, and fold the
  version and language menus into one component. The language menu links to
  the same page in each language through VitePress's own helper, and both
  menus close on Escape, on a click outside and when focus leaves.
- Components carry their own scoped styles; shell.css only restyles the
  default theme's frame, sidebar, outline and search.
- Style the Algolia modal through its variables where it has them.
- Draw the menu icons from Iconify instead of hand-written paths.
- The homepage drops code it never used: a hero view, text-on-face geometry,
  translations of strings it does not show, overrides for the nav bar it
  replaced, and the raw HTML bodies of its three index pages. The two
  cycling hooks are one.

Fixes
- The upgrade guide scrolled the whole page sideways on a phone and painted
  past the column rule on desktop. Its text now starts on the article's edge.
- A code group is one band, so inside a callout it no longer sits in a grey
  box of its own.
- Open menu chevrons pointed left instead of up.
- Search result icons were 48px tall with a stray rule across them.
- On a phone, nested quotes and footnotes overflowed the screen and the
  sidebar mark covered its label. Between 768px and 959px, bands stopped 8px
  short of the screen edge.

Checked by comparing computed styles and geometry of 48,000 elements across
10 pages at three widths, a cascade check that leaves no declaration that
never applies, and a code block audit over 78 pages.
- Open Collective carries its own logo in its brand colour instead of a
  generic heart.
- Contribution Guide and Add a Language draw from Lucide, whose strokes hold
  up at menu size better than Carbon's thin glyphs. Carbon is no longer used.
- Every menu icon is 20px, so brand marks, glyphs and the DevChalk avatar sit
  on one size instead of three.
- The mark is a fixed SVG drawing with two small tables: piece shapes and
  the request's stops. CSS turns it, pulls pieces out and draws the route
  on with pathLength, so the projection maths in geometry.ts and the Stage
  wrapper are gone. Every figure shares one viewBox and lines keep their
  pixel width with non-scaling strokes.
- Menu icons are Iconify classes from the Tailwind plugin, and GitHub uses
  the theme's own social icon, replacing the code that built SVG strings.
  The Community menu is built by one function instead of three copies.
- The syntax theme is one short scope table. The dark palette is gone with
  dark mode, which had no design and rendered code white on a light ground
  for readers whose system is dark.
- DocMeta reads the page's own file path and keeps one layer table;
  MethodIndex collects methods in one pass.
- Comments are kept only where they explain a workaround or a number.

Fixes
- The phone menu showed an external arrow on its own line under every
  Community row, because the theme only omits it for links holding an svg.
  It now uses the desktop panel's labels and spacing.
- At 1100px the version and language menus were squeezed by the search.
- Shiki's inline ground on each pre made code inside a callout grey again.

Checked with a computed style diff across 10 pages at three widths, every
code character on 11 pages, the breadcrumb and method index on all 74
English pages, and side by side screenshots of every mark state.
The first crumb uses the nav's own label for each language, and the layer label goes through the homepage translations, so Chinese and Uzbek pages no longer show English there.
Docs
- The method index only lists real method names, so Telemetry and the ORM
  guide no longer show their section headings as methods.
- Code block headers put the label on the article's edge. A first line that
  is only a file path comment becomes the header title.
- The sidebar marks the current page with colour and the diamond only.
- The layer label above an article links to the layers on the homepage.
- Small mono labels move from 11px to 12px.

Homepage
- Laravel's structure lists its concepts down the side and stacks the PHP
  and Go files, so it no longer repeats the tabs and panel above it.
- Start with the core runs the install as a sequence of commands, and the
  mark assembles beside it as each layer's pieces slide into place.
- The open source section ends on one row of links. The WeChat QR codes
  open on click instead of four blocks, and the footer is tighter.

@hwbrzzl hwbrzzl left a comment

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.

I'm thinking if it's better to remove the UZ language, given we don't have enough energy to maintain multiple languages, and it's easy to translate any language via browsers' AI.

- Above 1440px the homepage content box stopped every full-width rule short
  of the screen edge. The page margin now grows with the screen instead, so
  rules bleed to both edges at any width.
- The Laravel and Lite sections gain a top rule, so their column rule no
  longer starts in empty space.
- The docs sidebar keeps its width above 1440px. The theme widened it there,
  covering the start of the article.
- The pager's rule belongs to the pager, so it spans the band even without a
  previous page, and on a phone the next page's rule runs edge to edge.
- Link hairlines in the open source row are underlines, not borders.
@krishankumar01

Copy link
Copy Markdown
Member Author

I'm thinking if it's better to remove the UZ language, given we don't have enough energy to maintain multiple languages, and it's easy to translate any language via browsers' AI.

Yes, we can remove Uzbek in a separate PR so this diff stays reviewable.

Fixes
- The Chinese and Uzbek homepages linked facades and footer links to the
  English docs, and left stats labels, footer headings and the copyright in
  English. They now use the page's language, and the footer's language names
  are links.
- Menus and the WeChat QR codes did not close on a click outside in Safari,
  which does not focus a clicked button. One helper built on @vueuse/core
  closes them on an outside click, Escape, or focus leaving.
- @shikijs/transformers is back on v3, the version VitePress already uses,
  so the highlighter is not installed twice.
- Remove the old VitePress homepage styles and the emoji web font they
  loaded on every page.

Cleanups
- The uppercase label and the diamond marker are each defined once, with one
  letter spacing.
- Homepage columns and callout bands share their common rules, and the five
  code blocks render through one CodeLines component.
- External links and the version list live in links.ts. Contributors are
  names, with avatars built from them.
- useCycle uses vueuse for observing, timing, visibility and reduced motion.
- The locale sidebars keep master's formatting, i18n.ts moves to the theme
  root, and two type errors are fixed.
@hwbrzzl

hwbrzzl commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Awesome! A few nitpicks:

  1. Switch the font color of the button to white from black.
image
  1. ./artisan is for Mac or Linux, Windows can't use it. So I'm thinking if it's better to modify it to go run . artisan.
image
  1. Can the default color of the avatars be colored?
image
  1. I noticed the data is fixed. Can they be updated dynamically?
image
  1. The links of these two buttons are basically the same, maybe only leave one button or change one to another different link.
image
  1. How about switching the link to:
image
  1. Sort the nav
image
  1. Optimize the layout
image

@hwbrzzl hwbrzzl mentioned this pull request Sep 18, 2026
1 task
@goravel-coder

Copy link
Copy Markdown
Contributor

🤖 Automated review

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

Summary

PR #202 rebuilds the docs theme and homepage: a custom NavBar/SelectMenu, new VitePress slots (DocMeta, MethodIndex, NotFound, DocsFooterLinks), an animated home page, and 186 markdown files switched from blockquotes to callouts. The design work is solid, but the custom components bypass VitePress link normalization in three places (broken links under cleanUrls: false), and a newly enabled markdown-it plugin corrupts existing text in the release notes.

Verdict

  • Must Fix: 3 · Should Fix: 10 · Nits: 11

Findings

Must Fix

  1. .vitepress/theme/components/DocsFooterLinks.vue:9 Footer doc links omit .html — cleanUrls is false (.vitepress/config/shared.ts:62), and VitePress's normalizeLink appends .html only when rendering through VPLink. These paths (/getting-started/installation, /prologue/releases, /prologue/contributions) go into a raw <a :href>, so they resolve to paths that do not exist; every other new component in this PR (HomeFooter.vue:12-24, NotFound.vue:7-10, GoravelHome.vue:93) explicitly writes .html. Suggestion: append .html to the three doc paths, or render these through a link helper that applies normalizeLink.

  2. .vitepress/theme/components/NavBar.vue:41 Raw nav anchors bypass VitePress link normalization — the default VPNavBarMenuLink renders through VPLink, which calls normalizeLink() and appends .html when cleanUrls is false. This override emits bare <a :href="item.link">, so the configured nav links /getting-started/installation, /the-basics/routing, /architecture-concepts/request-lifecycle (.vitepress/config/en.ts:115/120/125, and the localized equivalents) become invalid paths. The same applies to the Community menu rows at line 55 / .vitepress/config/community.ts:44-45 (${prefix}/prologue/contributions). Suggestion: render with VPLink, or pass the configured links through normalizeLink() before binding.

  3. .vitepress/config/shared.ts:95 markdown-it-sub corrupts existing prose — single tildes are now parsed as <sub>. en/prologue/releases.md:5 (~Q1 and ~Q3) renders as (<sub>Q1 and </sub>Q3), and the same corruption hits zh_CN/prologue/releases.md:5 (~Q1和~) and uz_UZ/prologue/releases.md:5 (~Q1 va ~Q3). Suggestion: escape those lines (\~Q1) or drop md.use(sub).

Should Fix

  1. .vitepress/config/uz_UZ.ts:121 Uzbek "Add a Language" menu anchor is dead — it points at #add-a-new-language, but the target heading is ## Yangi Til Qo‘Shish (uz_UZ/prologue/contributions.md:82), which slugifies to something else, so the link never scrolls. Suggestion: use the localized slug, or add {#add-a-new-language} to that heading (as zh_CN already does with its own slug).

  2. .vitepress/theme/home/content.ts:159 Rate-limiter deep link only works in English — #rate-limiting matches ## Rate Limiting (en/the-basics/routing.md:218), but the zh heading is 速率限制 (zh_CN/the-basics/routing.md:218) and uz is Cheklov tezligi (uz_UZ/the-basics/routing.md:218), so link() produces /zh_CN/... + #rate-limiting and the localized homepage lands at the top of the page. Suggestion: add explicit {#rate-limiting} ids to the localized headings.

  3. .vitepress/theme/home/GoravelHome.vue:206 Translation keys missing for HTTP, Artisan and Your code — tr(l.name) at line 206 renders the bare layer names, but .vitepress/theme/i18n.ts only defines Application/Core/Data/Async, not HTTP; Artisan (rendered from CONCEPTS at line 172) and the literal 'Your code' returned by nameOf() at line 17 are also absent from both dicts. Chinese and Uzbek readers see English. Suggestion: add the three keys to both dictionaries, and consider stable message IDs instead of English source strings so future copy edits cannot silently drop a translation.

  4. .vitepress/theme/home/GoravelHome.vue:31 View captions are untranslated English — the caption strings (handled by, of 30 facades installed, The ${...} piece, pulled out of the mark) bypass tr() and are rendered as aria-label in GoravelMark.vue:44, so screen-reader output is English on /zh_CN and /uz_UZ. Suggestion: build captions via tr() or compose them from already-translated stop/layer names.

  5. .vitepress/theme/components/NotFound.vue:18 404 page copy is hardcoded English — "This page does not exist.", the lead paragraph and "Try these" all bypass useI18n(), unlike every other new surface in this PR. Suggestion: route them through tr() with new dict entries.

  6. .vitepress/theme/components/MethodIndex.vue:57 Hardcoded user-facing strings in a global component — "Methods" (line 57), placeholder="Filter" (line 63) and aria-label="Filter methods" (line 64) render on every reference page in every locale. Suggestion: move them into useI18n().

  7. en/architecture-concepts/facades.md:66 Conversion left labelled blockquotes behind, inconsistently per locale — 8 notes survived the 186-file sweep: en/architecture-concepts/facades.md:66, en/security/authorization.md:124, zh_CN/architecture-concepts/facades.md:66, zh_CN/security/authorization.md:124, zh_CN/digging-deeper/filesystem.md:18, :88, :338, uz_UZ/the-basics/validation.md:148. Several are ::: warning/::: tip in the other locales (e.g. en/digging-deeper/filesystem.md:338), so callout rendering differs by language for the same note. Suggestion: finish the sweep and align the three locales.

  8. .vitepress/theme/index.ts:4 Homepage bundle is shipped to every doc page — GoravelHome.vue is statically imported and globally registered (line 22), pulling in GoravelMark, CodeLines, CommunitySection, HomeFooter, content.ts and home.css on all 180+ doc pages even though it renders only on the three layout: goravel-home pages. Suggestion: register it with defineAsyncComponent(() => import('./home/GoravelHome.vue')) and move the home.css import into the component so Vite can code-split it.

  9. .vitepress/theme/goravel.css:1 Render-blocking remote font import — a top-level @import url('https://fonts.googleapis.com/css2?...Mona+Sans:wdth,wght@75..125,200..900...') serializes the critical path (bundled CSS → Google CSS → font file) on every page for a two-axis variable font. Suggestion: move it to head with <link rel="preconnect"> + <link rel="stylesheet">, and consider dropping an axis or subsetting.

  10. .vitepress/theme/Layout.vue:14 Coverage bars are injected by scraping rendered markdown DOM — drawCoverage queries .vp-doc table td:last-child, regexes ([\d.]+)\s*% out of cell text and appends raw HTML on mount and every route change. This couples the theme to the exact table markup of getting-started/packages, bypasses VitePress's render pipeline, and silently stops working if that table changes. Suggestion: emit the coverage markup from a markdown-it transform or a data-driven component rather than mutating rendered DOM.

  11. .vitepress/config/zh_CN.ts:29 Locale sidebar order drifts from en/uz — en.ts and uz_UZ.ts place AI before Security, while zh_CN.ts places 安全 before AI; the three ~330-line locale configs are hand-copied with no shared factory, so they will keep diverging. Suggestion: define the sidebar tree once and pass translated label records per locale.

Nits

  1. .vitepress/theme/index.ts:38 Giscus locales omit Uzbek — only zh-CN and en are mapped, so /uz_UZ pages fall back to the English comment UI. Suggestion: add a uz entry.
  2. .vitepress/theme/components/NavBar.vue:65 Hardcoded version label — label="v1.18" duplicates VERSIONS[0].text in .vitepress/links.ts. Suggestion: derive the label from the selected option so they cannot drift.
  3. .vitepress/theme/home/GoravelHome.vue:74 Magic number 30 duplicated — ... of 30 facades installed is also hardcoded as / 30 at line 250 while FACADES already holds exactly 30 entries. Suggestion: derive it from Object.keys(FACADES).length.
  4. .vitepress/theme/home/HomeFooter.vue:46 Footer logo lacks intrinsic width — <img src="/logo@2x.png" height="24" /> has no width, so no aspect ratio is known before load (logo@2x.png is 178×48). Suggestion: add width="89", as NavBar.vue:35 already does.
  5. .vitepress/theme/home/CommunitySection.vue:9 Stats and contributor list are frozen in presentation code — the star/fork counts, v1.18 and the 43-name PEOPLE list are literals here, not in home/content.ts; v1.18 also duplicates links.ts. Suggestion: move the data to content.ts and source the version from VERSIONS.
  6. .vitepress/theme/components/MethodIndex.vue:29 :has() inside querySelectorAll throws on engines without support — the whole selector list fails, silently disabling the method index for every page. Suggestion: select .vp-doc h3, .vp-doc h4, .vp-doc p > strong and filter in JS.
  7. .vitepress/theme/components/DocMeta.vue:40 Unsafe cast of theme.sidebar — theme.value.sidebar as { text: string; base: string }[] then groups.find(...) assumes an array with a base on every group; a locale-keyed sidebar map would throw and break the doc header. Suggestion: guard with Array.isArray and an optional base.
  8. .vitepress/config/community.ts:16 Nav config emits markup and inline colors into the view layer — row() returns an HTML string with class="g-menu-row" and style="color:…" that NavBar.vue:55 injects via v-html. Suggestion: pass structured fields (icon, variant) and render them in the component with CSS variables.
  9. .vitepress/theme/i18n.ts:3 Deep import of a VitePress internal — vitepress/dist/client/theme-default/composables/langs.js is a private path in an alpha release (2.0.0-alpha.4) and can break on upgrade. Suggestion: prefer public exports from vitepress/theme, or vendor the minimal behavior.
  10. .vitepress/theme/home/HomeFooter.vue:9 Footer columns duplicate the sidebar taxonomy — COLUMNS re-declares labels and paths already maintained in config/en.ts, zh_CN.ts, uz_UZ.ts, creating a second source of truth for navigation. Suggestion: derive footer links from a shared section registry.
  11. .github/workflows/deploy.yml:3 No pull-request build or link check — both workflows are workflow_dispatch-only, so VitePress's build-time dead-link/manifest errors in a 218-file docs change only surface on a manual deploy. Suggestion: add a pull_request job running pnpm install --frozen-lockfile && pnpm docs:build.

Automated Checks

  • prettier --check on the 23 changed .ts/.vue files — 22 reported unformatted, but the same check fails on unmodified files (e.g. .vitepress/config/index.ts) and on origin/master, so this is pre-existing repo-wide drift, not introduced here.
  • Callout fence balance scan across the 186 changed markdown files (::: type vs :::) — no unbalanced callouts; zero [[toc]] lines remain.
  • pnpm docs:build — not run (dependencies are not installed in the review worktree).

- Each piece of the mark uses the path and colour of public/logo.svg: light
  blue on top, cyan on the right, dark teal on the left, flat with no
  outlines. The three colours are tokens used only by the mark.
- A piece out of focus becomes a pale tint of its own colour, so the logo
  stays recognisable. Your own code (the Service stop) leaves every piece
  pale, and a layer Lite has not installed waits outside as an outline.
- The request route and its marker are drawn in ink, so they read on any of
  the blues. The rotation in the Laravel section is gone.
- Code blocks no longer show a scrollbar when the code fits. The tint of a
  lit line bleeds past the text from the block, not from each line.
- Get started has white text. It sits on the deeper cyan, since white on the
  bright cyan is 2.95:1 and unreadable for many; on the deeper one it is
  4.9:1. Hover turns the button ink.
- The homepage runs Artisan as go run . artisan, which works on Windows
  too, in the Lite steps and the Laravel comparison.
- Contributor avatars are in colour.
- The second hero button opens Compare with Laravel instead of a page next
  to the one Get started opens.
- The sidebar starts with Prologue and Upgrade again, as on master, in all
  three languages.
- Less space above section headings in the docs: 60px instead of 84px.
Past 1440px the docs article stayed 800px and the outline column took the
rest, which left most of a wide screen empty. The site now stops at 1440px,
the width its homepage and docs are laid out for, and sits centred with a
border on each side running top to bottom. Every full-width rule ends on
those borders.

The nav, the sidebar and the Community menu are fixed to the screen, so they
are placed inside the frame by the same offset. Nothing changes at 1440px or
below.
@krishankumar01

Copy link
Copy Markdown
Member Author
  1. Switch the font color of the button to white from black.
image

2 ./artisan is for Mac or Linux, Windows can't use it. So I'm thinking if it's better to modify it to go run . artisan.
3. Can the default color of the avatars be colored?
7. Sort the nav

Done

  1. Optimize the layout

For this, I’ve added a maximum width up to which it can stretch. After that, it will stay fixed.
image
image

- The nav and the Community menu render their own links, so they skipped
  the default theme's rule that adds .html to page links while clean URLs
  are off. They now use the theme's normalizeLink, the same helper as the
  default nav. The docs footer writes .html like the homepage footer does.
  All links now match the pages' real addresses, not a server fallback.
- markdown-it-sub is removed. No page uses subscript, and it turned the
  Chinese release notes' (~Q1和~Q3) into a subscript "Q1和".
- Stars and forks come from GitHub in the visitor's browser, without a
  token. The answer is kept for an hour, and a refusal also waits an hour,
  so a visitor asks GitHub at most once an hour.
- Until that answer arrives, or if GitHub refuses, the page shows the
  numbers read when the site was built. If the build cannot reach GitHub
  either, it uses a saved copy, so a build never fails.
- The current release is read at build time. The contributor count is the
  length of the hand-kept list, so it always matches the avatars shown.
@krishankumar01

Copy link
Copy Markdown
Member Author

@hwbrzzl Done, the stats are live now. They come from GitHub's public API, so no token is needed.

That API only allows 60 requests an hour per visitor, so I added a few fallbacks:

  • When we build the site, it grabs stars, forks and the latest release from GitHub, so the page always has real numbers from the start.
  • When someone opens the homepage, their browser fetches the live stars and forks and updates them.
  • The browser remembers that for an hour, so the same person won't hit GitHub again on every visit.
  • If GitHub says no (rate limit or timeout), the page just keeps the last numbers it had and tries again an hour later.
  • And if the build itself can't reach GitHub, it falls back to a saved copy, so the build never breaks.

For contributors, I kept the list manual, and the count comes from that list so it always matches the avatars.

Chinese
- The 404 page, the method index and "Your code" on the homepage are
  translated, and the 404 page links stay in the reader's language.
- The homepage drawing's screen reader labels are built from translated
  words instead of English sentences.
- The rate limiting heading has the id rate-limiting, so the homepage's
  RateLimiter link lands on it. The v1.10 upgrade guide's link follows.

Docs
- Notes still written as "Notice:" or "注意:" quotes are callouts, with the
  same type in English and Chinese.

Loading
- The homepage component loads only on the homepages. The code every doc
  page downloads drops from 140KB to 113KB (45KB to 36KB gzipped).
- Fonts are requested from the page head, so they download alongside the
  stylesheet instead of after it.
…ndings

- The packages page draws its coverage bars when the site is built, so they
  no longer depend on a script reading the rendered table afterwards.
- The Chinese sidebar lists AI before Security, as English does.
- The version label, the thirty facades and the contributor count each come
  from the list they describe instead of a second copy of the number.
- The contributor list sits with the homepage's other data.
- The method index avoids :has(), which a browser without support rejects
  for the whole selector, leaving no index at all.
- The breadcrumb copes with a sidebar that is not a list of groups.
- The footer logo reserves its width, so the footer does not jump.
- A pull request is built the way a deploy is, so dead links and build
  errors show up before merging.
Replace the request-flow and five-layer sections with a page that shows
what Goravel ships, using the brand mark as art instead of a diagram.

- Hero: the mark on its isometric grid, one call to action and a
  copyable install command
- Laravel, line for line: PHP and Go pairs with matching lines
  highlighted, keyboard-accessible tabs that tour once and stop when
  the reader picks one
- 30 facades, one framework: an alphabetical facade index plus six
  capability cells with verbatim snippets (one process, gin or fiber,
  Docker-backed tests, AI SDK, telemetry, one binary)
- Start with the core: the mark assembles as Lite facades are installed,
  with a running count
- Open source: live stars, a release and support timeline read from
  prologue/releases.md, core team, contributors and ways to join

All release-specific content lives in .vitepress/theme/home/config.ts.
The page is split into one component per section, GoravelMark is reduced
to pieces, grid and states, and home.css drops the rules for the removed
sections. Unused translation keys are removed and Chinese strings added
for the new copy.

Also:
- nav: replace the mislabelled Guides and Framework links with Packages
  and Releases
- load the default theme without its bundled Inter fonts
- docs: fix two broken links in compare-with-laravel.md and the core
  developer profile link in contributions.md
- task-scheduling: replace the Chinese table headers on the English
  page with Method and Description, and fix the "Crone" typo
- artisan-console: the route handler used *gin.Context, which is not a
  Goravel handler. Use func(ctx http.Context) http.Response in the
  English, Chinese and Uzbek pages, and Route().Get instead of the
  nonexistent GET on the Chinese page
- homepage: update the title, description, Open Graph and Twitter text
  in English and Chinese to match the new page, and replace meta.png
  with a card rendered from the new hero
Clicking a tab left the section dead: mouse focus and hover both paused
the line highlight, and a manual pick stopped the tab tour for good.

- the line highlight keeps running after a mouse click, even while the
  pointer rests on the card
- the tab tour resumes 8 seconds after a manual pick, once the pointer
  has left the card
- hover only holds the tabs in place, so the code cannot switch while it
  is being read
- keyboard focus (focus-visible), reduced motion, off-screen and a
  hidden browser tab still pause everything

Also fix the tour stalling on tabs with a single matching pair (Events,
Artisan): the highlight never moved there, so the tour never advanced.
Every tab now stays for at least three beats before moving on.

Replace useCycle with a smaller useTicker and move the tour logic into
HomeLaravel, its only user.
Notes written as plain "Note:" lines, emoji "Tip:" lines or blockquotes
now use the tip, info and warning containers the rest of the docs use.
Applied to the same spots in English and Chinese.

- packages: info and tip containers, the how-to steps moved inside the
  tip as a numbered list, stray asterisk removed from the table header
- facades, authentication, orm, routing, testing, filesystem,
  installation, configuration: blockquotes and "Note:" lines converted,
  each typed by what it says (tip, info or warning)
- task-scheduling: the "one server" note listed memcached and dynamodb,
  which Goravel has no drivers for. It now says the default cache must be
  shared by all servers, such as redis, and that memory will not work
- installation: remove emoji from headings
- normalise ":::tip" to "::: tip" in both languages
The page showed two status columns of check marks and an unlabelled
"code example" column in plain text, with absolute links that broke on
the versioned sites and sent Chinese readers to English pages.

- one table, Feature, Laravel, Goravel, with real inline code on both
  sides and relative links. Applied to English and Chinese
- correct four Goravel examples that did not match the API: Gate.Allows
  takes a map, Mail.To takes a slice, ValidateRequest needs an argument,
  and Event and Queue Job need the args slice
- add examples to the rows that had none (Testing, Mock, Package
  Development) and rename labels to match where they link: Routing,
  Migrations, Logging, ORM
- where one framework lacks a feature, the cell shows its mark greyed
  out with a cross and the words, instead of an emoji

Add a Brand component for docs pages: <Brand laravel />, <Brand goravel />
and the "no" variant. The table headers use it.

Pages with "aside: false" now take the full content width instead of
leaving an empty right rail, and feature names in the first column of
doc tables no longer break mid-word.
…s en and zh_CN

- correct Avg, AcceptJSON and View().Exists to match the framework
- use # comments in shell blocks so copied commands run
- repoint 34 broken anchors, add the missing Cache Key Prefix section
- require Golang 1.25, mention Goravel Lite in the installer step
- label 22 code blocks, drop Eloquent wording, fix typos
@goravel-coder

Copy link
Copy Markdown
Contributor

🤖 Automated review

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

Summary

Re-review at 84de43a72. The follow-up rework is substantial and fixes most of the previous round: nav/footer links now go through normalizeLink, markdown-it-sub is removed, the homepage is code-split and split into Home*.vue + config.ts, fonts load via <link> instead of @import, coverage bars moved into a markdown-it transform, and a PR build workflow was added. Two user-visible regressions remain: the layer badge on every doc page links to an anchor that no longer exists, and the '<Layer> layer' translation keys were deleted so those labels are now English on zh_CN. (uz_UZ-only findings were intentionally dropped, since that locale is being removed.)

Verdict

  • Must Fix: 2 · Should Fix: 7 · Nits: 10

Findings

Must Fix

  1. .vitepress/theme/components/DocMeta.vue:59 Layer badge links to a section that no longer exists — the homepage rebuild replaced the old GoravelHome.vue <section id="layers"> with HomeFacades.vue, which sets no id (.vitepress/theme/home/HomeFacades.vue:11), so link('/#layers') on every doc page now lands at the top of the homepage instead of the facades section. Suggestion: add id="layers" to the facades section, or point the badge at the real target.

  2. .vitepress/theme/i18n.ts:20 Layer-label keys were deleted, so layer badges render in English on zh_CN — the rewrite dropped 'HTTP layer', 'Application layer', 'Core layer', 'Data layer', 'Async layer' (and the bare HTTP), but DocMeta.vue:59 still calls tr(\${meta.layer} layer`). Chinese doc pages now show e.g. "Core layer" untranslated. Suggestion: restore the ' layer'keys, or compose the label from the short keys and add the missingHTTP` entry.

Should Fix

  1. .vitepress/theme/home/home.css:414 Footer grid declares one column too many — grid-template-columns: minmax(0, 6fr) repeat(3, minmax(0, 2fr)) but FOOTER (.vitepress/theme/home/config.ts:234) has only 2 groups, so the homepage footer renders an empty trailing column and squeezes the real ones left. Suggestion: repeat(2, minmax(0, 2fr)).
  2. .vitepress/theme/home/GoravelMark.vue:25 Pieces are zipped positionally with ORDER, with no guard — mark.pieces is a regex-scrape of public/logo.svg (mark.data.ts:28) mapped by index onto ORDER = ['core','data','async','app','http']; if the logo gains, loses or reorders a class="sN" path, PULL[ORDER[i]] throws during render and takes down every page that mounts the mark. Suggestion: assert pieces.length === ORDER.length (and known fills) at load time, or key pieces by the SVG class.
  3. .vitepress/theme/home/github.data.ts:29 Unused release fetch, and CI never supplies GITHUB_TOKEN — the loader fetches repos/goravel/framework/releases/latest into a release field nothing reads, and .github/workflows/build.yml passes no env, so process.env.GITHUB_TOKEN (line 17) is always undefined and every build spends unauthenticated calls against the shared 60 req/h budget before silently falling back to SAVED. Suggestion: drop the release request/field and export GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} on the build step.
  4. .vitepress/theme/home/HomeLite.vue:41 The Lite autoplay loop bypasses the new shared ticker — it uses raw useIntervalFn plus a useIntersectionObserver that stop()s after the first intersection (line 47), so steps keep advancing while the tab is hidden or the section is scrolled away, unlike useTicker used by HomeLaravel.vue:22. Suggestion: drive the step advance from useTicker.
  5. .vitepress/theme/home/HomeOpenSource.vue:21 Three competing sources of truth for the GitHub numbers — the build-time loader (github.data.ts), a client useFetch on mount, and a useLocalStorage cache with three different fallbacks; the displayed value depends on which path wins. Suggestion: keep the loader for the build-time value and use the client refresh only for staleness, sharing one fallback.
  6. .vitepress/theme/home/releases.data.ts:23 day() can abort the whole build — for any cell that is neither Qn, YYYY nor a parseable date (e.g. TBD), new Date(...).toISOString() throws RangeError: Invalid time value, failing pnpm docs:build with an opaque error. Suggestion: validate with Number.isNaN(date.getTime()) and fall back to the raw text, naming the offending row.
  7. .github/workflows/build.yml:14 Dependencies install before Node is selected — pnpm/action-setup@v4 with run_install: true runs on the runner's default Node, before actions/setup-node applies lts/*, so the cache: pnpm store restore and the intended Node version don't apply. Suggestion: move Setup Node above Setup pnpm, set run_install: false, and run pnpm install --frozen-lockfile explicitly.

Nits

  1. .vitepress/theme/components/NavBar.vue:28 new RegExp(activeMatch) is unguarded and unvalidated — a malformed activeMatch in any locale's nav config throws during render, and nothing checks these patterns at build time. Suggestion: precompile once, wrapped, or add a build-time validation test.
  2. .vitepress/theme/components/NavBar.vue:26 VERSIONS.find((v) => v.selected)!.text throws if nothing is flagged selected — guard and fall back to a default label.
  3. .vitepress/theme/components/NavBar.vue:58 v-html of config-authored HTML persists — .vitepress/config/community.ts:16 builds row markup with inline brand colors, so presentation still lives in the data layer and any future dynamic label becomes an injection sink. Suggestion: emit structured { icon, variant } and render it in the component.
  4. .vitepress/theme/i18n.ts:3 New deep imports into VitePress internals — vitepress/dist/client/theme-default/composables/langs.js and .../support/utils.js (NavBar) are private paths in an alpha (2.0.0-alpha.4), and .vitepress/config/shared.ts:170 still aliases any */VPNavBar.vue. Suggestion: prefer public exports, and narrow the alias to the resolved package path.
  5. .vitepress/theme/index.ts:40 The giscus en locale key can never match — the plugin reads <html lang>, which is en-US, so the en entry is dead and English pages rely on the default lang: 'en' by luck; the map is misleading and any future locale needs its real tag. Suggestion: key it as 'en-US': 'en' alongside 'zh-CN': 'zh-CN'.
  6. .vitepress/theme/home/config.ts:234 FOOTER duplicates the sidebar taxonomy — labels and paths are maintained again here alongside config/en.ts and config/zh_CN.ts. Suggestion: derive footer links from one shared route table.
  7. .vitepress/theme/home/config.ts:135 FRESH duplicates CAPABILITIES[].facade — a "new" facade must be listed twice and can disagree. Suggestion: derive FRESH from CAPABILITIES.
  8. .vitepress/theme/components/Brand.vue:2 The goravel prop is declared but never read — <Brand goravel /> is used in en/prologue/compare-with-laravel.md:9 and silently falls through the v-else logo branch, so the prop contract is misleading. Suggestion: remove goravel (make the logo the default) or actually use it.
  9. .vitepress/theme/home/HomeOpenSource.vue:37 now is assigned in onMounted before its const declaration — line 25 writes now.value while const now = ref(plan.now) is declared at line 37; legal only because the callback runs later, but it reads as use-before-declaration. Suggestion: declare the refs above the lifecycle hook.
  10. .vitepress/theme/home/mark.data.ts:29 fills.get(m[1])! may be undefined — a class="sN" path with no matching .sN { fill: … } rule produces an unfilled piece and later throws in tint() (GoravelMark.vue:56) for dim state. Suggestion: skip pieces without a fill or fall back to a default color.
  11. .vitepress/theme/home/useTicker.ts:17 Hardcoded 700 ms first interval differs from ms — wait only becomes ms after the first tick, making the first animation step inconsistent. Suggestion: initialize wait from ms.

Automated Checks

  • prettier --check on changed theme files — all fail, but untouched files at origin/master fail identically, so this is pre-existing repo-wide drift and was not raised as a finding.
  • Callout scan across en/zh_CN — fully converted; no labelled blockquote notes remain.
  • Anchor/existence checks — id="layers" no longer exists anywhere in the theme; normalizeLink confirmed to append .html while cleanUrls: false.
  • pnpm docs:build — not run (dependencies are not installed in the review worktree).

Resolved since the first review: footer/nav .html links, markdown-it-sub tilde corruption, homepage bundle shipped to doc pages, Google Fonts @import, HomeFooter logo width, avatar grayscale filter, duplicated magic 30, hardcoded v1.18 label, DocMeta unsafe sidebar cast, English-only NotFound/MethodIndex strings, zh_CN sidebar order, coverage-bar DOM scraping, :has() in MethodIndex querySelectorAll, and the missing PR build.

One note for the eventual uz removal: the locale name is hardcoded in several places (DocMeta.vue:37 regex, i18n.ts DICTS, shared.ts Algolia locales, config/index.ts), so that cleanup will need to touch all of them.

- layer badge is a plain label now that the homepage has no layers section
- restore the layer label translations for zh_CN and uz_UZ
- footer columns follow the FOOTER config instead of a fixed count
- fail the build clearly when logo.svg pieces or a release date cannot be read
- fetch only the star count and pass GITHUB_TOKEN to the docs build
- giscus en-US locale key, Brand goravel prop, version label fallback
…ry pages

Neither page has section headings, so the "On this page" column rendered empty
with its rule. Both now set aside: false, as the Laravel comparison page does,
in en, zh_CN and uz_UZ. The content takes the full width and the package
descriptions fit on one line.

Also on the packages page: goravel/gemini (51.8%) moves above goravel/sqlite
(45.2%) so the table is in coverage order as its note says, and the English
intro is reworded.

@hwbrzzl hwbrzzl left a comment

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.

Awesome, amazing work 👍

@krishankumar01
krishankumar01 merged commit c6c15ce into master Sep 20, 2026
1 check passed
@krishankumar01
krishankumar01 deleted the kkumar-gcc/home-page-redesign branch September 20, 2026 10:38
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