Skip to content

fix(reader): match frappe-ui docs chrome#726

Merged
NagariaHussain merged 10 commits into
developfrom
fix/reader-ui-polish
Jul 26, 2026
Merged

fix(reader): match frappe-ui docs chrome#726
NagariaHussain merged 10 commits into
developfrom
fix/reader-ui-polish

Conversation

@NagariaHussain

Copy link
Copy Markdown
Member

Closes #724.

Problem

The public reader drifted from the frappe-ui docs it's modelled on: mismatched borders, a TOC label that didn't line up, a cramped mobile drawer, and hand-copied SVGs that had quietly become Heroicons. Change request lists showed a bare spinner while loading.

Solution

Audited against the frappe-ui source (vitepress/components/Docs/* and the component recipes), not guesswork.

Chrome

  • Sidebar is white with its hairline border; exactly one rule ends the chrome — the tab row when tabs exist, the navbar when they don't.
  • No smooth scrolling; anchor jumps land instantly.
  • Buttons, dropdowns and menu rows use frappe-ui's own class recipes (Button, Menu, ItemListRow).

Icons

  • Chrome icons now come from the generated lucide table instead of hand-pasted paths. Several had drifted to Heroicons — the magnifier, hamburger, check, list and clipboard were a different glyph from the docs'. Brand marks stay inline; lucide dropped its brand set.

TOC

  • h3s indent only when the page also has an h2, matching OnThisPage.vue, so the "On this page" label lines up on all-h3 pages without losing hierarchy elsewhere.

Mobile

  • Nav toggle is the floating Menu pill; the drawer is a fixed 80dvh sheet with touch-height rows and a search field that filters the tree, per MobileNavSheet.vue.

Change requests

  • Skeleton rows in the real table chrome instead of a centred spinner, so nothing shifts when data lands.

Also fixes two bugs found on the way: the mobile tab picker used an undefined --surface-white token so it rendered with no background, and the icon macro's default size silently overrode a caller's size-[18px].

NagariaHussain and others added 8 commits July 26, 2026 16:37
Closes #724.

- TOC: every "On this page" link sits at pl-4 regardless of heading level,
  so the label lines up with the rail on pages that open with an h3.
- Sidebar: white (surface-base) and borderless; navbar drops its bottom
  border too. The tab row keeps its rule, so the chrome still reads as one
  band instead of stacking hairlines.
- No smooth scrolling: anchor jumps land instantly.
- Mobile: the nav toggle is a floating "Menu" pill pinned bottom-right
  (frappe-ui Button outline/lg), and the sheet is a fixed 80dvh panel on
  surface-base. The ⌘K palette drops its keyboard hints below sm, where
  they only wrap into a cramped two-line block.
- Buttons, dropdowns and menu rows use the frappe-ui recipes verbatim
  (Button size/variant classes, Menu content, ItemListRow); the tab
  picker's bg-[var(--surface-white)] was an undefined token, so that
  dropdown had no background at all.
- Tree toggles use frappe-ui Tree's stroked chevron-right in a size-5 slot.
- Hand-written SVGs drop to stroke-width 1.5, the width frappe-ui
  normalises lucide to — at 2 they read heavier than the same icon in
  the docs.
- Drop the unused ghost_button / dropdown / icon_button macros rather
  than restyle markup nothing renders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replace the centred spinner with skeleton rows rendered inside the real
List chrome, so the column tracks and row height are already correct when
the data lands — no jump from a blank panel to a table. Bar widths vary
per column kind so it reads as a table rather than a block of identical
grey lines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The docs navbar renders its search field as `hidden ... md:flex`, so on
mobile the only search bar is the one inside MobileNavSheet — a frappe-ui
TextInput (size lg, subtle, prefix slot) that filters the nav list. The
reader had no equivalent, which is why the mobile search never matched.

Add it to the bottom sheet with TextInput's exact lg recipe (h-10,
rounded-md, ps-10 for the prefix, subtle border/bg/hover/focus chain),
filtering the tree in place: a row survives if its own title matches or a
descendant's does, and matched groups expand so the hit is visible. The
header's magnifier still opens the full-text palette — that's a wiki
feature the docs don't have.

Also from the same source:
- sheet/overlay transitions use the docs' cubic-bezier(0.32,0.72,0,1) at
  300ms in / 200ms out; overlay is black/40; the Menu pill hides while the
  sheet is open, as MobileNavSheet's `v-if` does.
- drag handle geometry (pt-2 pb-1, w-10, surface-gray-3).
- palette gains the ⌘↵ "new tab" hint it already supported, and `esc` /
  `K` take the text-sm sizing CommandPalette gives them.
- search trigger hover border is outline-elevation-2, not outline-gray-2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reader's icon macro had hand-pasted SVG paths, and most had drifted to
Heroicons: the magnifier was a circle-arc (`a 7 7 0 11-14 0`) where lucide
draws `circle r="8"` plus a stub, and menu / check / list / clipboard /
chevron / file-text were all a different glyph from the one frappe-ui docs
renders. Only command, the arrows, sun and moon-star happened to match.

Generate them instead, through the table the tab icons already use, so the
reader draws the same pack at the same 1.5 stroke as the SPA and the docs:
add a CHROME_ICONS list to generate-public-lucide.mjs and have render_icon
resolve those names via lucide_svg. Brand marks (GitHub, Discord, X, …)
stay inline — lucide dropped its brand set, which is why the docs navbar
hand-writes its own GitHub mark too. Inline Heroicons chevrons and ticks
left in the navbar, mobile header and split button go through the macro
as well.

Also stop the macro's default w-4/h-4 from overriding a caller's size:
`size-[18px]` on the sheet's search prefix was rendering 16px, because
Tailwind emits w-*/h-* after an arbitrary size-*. It now only applies the
default when the caller passed no size utility.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reading vitepress/components/Docs/OnThisPage.vue instead of guessing from
the rendered DOM corrects an earlier overcorrection and turns up two more
mismatches.

The indent rule is `'pl-7': x.type == 'h3' && h2Exists` — h3s are indented
only when the page also has an h2. Flattening every link (the earlier fix
for the misaligned label) was right for the all-h3 page it was tested on
and wrong everywhere else, since it threw away the hierarchy on mixed
pages. Gate it on an h2 being present instead: the label stays aligned on
all-h3 pages and h3s nest again when there is an h2 to nest under.

Also:
- the rail is a bare `border-l`, i.e. the default outline-gray-1, not
  outline-gray-2 — ours was drawing a step darker than the docs'.
- the aside is w-[200px], not w-56 (224px).
- the label carries no colour class and no transition; it inherits, and
  our body ink already resolves to the same value.

Both halves of the component are updated — the Jinja render and the
nav-store's SPA re-render — and verified to agree on an all-h3 page, a
mixed page, and after client-side navigation between them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bottom sheet reused the desktop tree verbatim — 28px rows at text-sm —
where frappe-ui docs' MobileNavSheet gives its links h-11, rounded-md and
text-md, a real touch target instead of a packed line. Add a `dense` flag
to render_wiki_tree (desktop keeps h-7) and have the sheet render at touch
density, hover/active landing on surface-gray-2 / ink-gray-8 like the docs'.

Drop the sheet's three internal rules as well: the docs sheet has no
dividers at all, its only bordered element is the search field. The header
row, space switcher and navbar-link footer each carried one.

The shadow needed nothing — sheet and pill already resolve to the same
four-layer shadow-2xl and 20px top radius as the docs'. Verified by
comparing computed styles side by side.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removing it left a white tree on a white page with nothing to sit against.
The current Frappe docs layout (ffdocsnew) separates the two with a
hairline — a bare `border-r`, i.e. the default outline-gray-1 — so take
that back, including the width animating to 0 on collapse so a collapsed
sidebar leaves no stray line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The chrome's bottom hairline belongs to whichever row ends it. With tabs
that's the tab row; without them nothing carried it, so the sidebar's
border started in mid-air below a borderless navbar. Give the navbar the
rule only when no tab row follows, so exactly one line runs across the
window either way and the sidebar's border meets it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the prior hidden-tab filtering issue is fixed by limiting matches to visible subtrees.

Reviews (3): Last reviewed commit: "fix(reader): a lone next link sits left,..." | Re-trigger Greptile

Comment thread wiki/templates/wiki/includes/mobile_header.html Outdated
Every tab's subtree is in the DOM; only the active one is displayed. The
filter walked all of them, so a query matching a page in an inactive tab
kept navHasResults true while nothing visible survived — a blank drawer
with the no-results line suppressed. Count and hide only within displayed
subtrees. Clearing still spans every subtree, so switching tabs can't
surface rows left hidden by an earlier query.

Reported by Greptile on #726.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@NagariaHussain

Copy link
Copy Markdown
Member Author

Good catch — reproduced it before fixing.

On a space with tabs, searching for a page that only exists in an inactive tab gave 0 visible rows and a suppressed no-results line, i.e. a blank drawer. Every tab's subtree is in the DOM; only the active one is displayed, and the filter walked all of them.

Fixed in ca50e52: matching and result-counting now run only within displayed subtrees. Clearing still spans every subtree, so switching tabs can't surface rows left hidden by an earlier query.

Verified on a tabbed space (match in hidden tab → no-results shown; match in active tab → row shown; no match → no-results) and on a space without tabs, where the tree is a direct child of the nav rather than wrapped in a tab container.

On a first page there is no previous link, and the `ml-auto` shoved the
only button against the right edge with nothing opposite it. frappe-ui
docs' PrevNextBtns adds no margin utility for that case: `justify-between`
already leaves a single child at the start. Drop it from both the Jinja
render and the nav-store's SPA re-render.

Verified server-side and after client-side navigation: a lone next button
starts at the container's left edge, and with both links present they
still sit at the two ends.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@NagariaHussain
NagariaHussain merged commit 7ad53fe into develop Jul 26, 2026
10 checks passed
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.

UI polish to match Frappe UI docs

1 participant