Update the delta / gamma / theta / vega / rho response-field
descriptions on the Calc Index (calc_indexes) interface across all three
languages (zh-CN / zh-HK / en).
Key change: **theta now documents the per-day semantics** — the raw
value has been divided by 365 on the server to convert to a daily value
(previously the docs incorrectly said divide by 100). delta/gamma get
full descriptions; vega/rho reworded to the /100 form.
Source of truth: the 期权希腊字母 spec table. Matches the SDK
(longbridge/openapi#586), MCP, and CLI changes.
## Problem
The `history_executions` docs (zh-CN + en) list only `symbol` /
`start_at` / `end_at` in the parameter table, and the response section
states "`has_more` is `true` when results exceed 1000" — but never
document **how** to page. The sibling `all_executions` doc does document
`page`. So a reader hitting the 1000-record cap has no documented way to
paginate.
This surfaced downstream in longbridge/longbridge-terminal#320, where a
consumer building a ledger assumed windowed queries silently truncate at
1000 with no way forward.
## Change
Add the `page` parameter row to `history_executions.mdx` (zh-CN + en),
copied from `all_executions`.
**Verified against the production gateway**: `page` is 1-based and
respected — on an account with N (<1000) fills, `page=1` returns all N
and `page=2` returns empty; per-page size is fixed at 1000 (no size
param).
Refs longbridge/longbridge-terminal#320
Fixes#1249.
The Java example for the rank-list (人气排行榜) API called
`ctx.getRankList("hot_all-us", false)` with positional args, but the
Java API takes a `RankListOptions` object:
```java
RankListOptions opts = new RankListOptions();
opts.key = "hot_all-us";
opts.needArticle = false;
var resp = ctx.getRankList(opts).get();
```
Updated the Java example in all three languages (en / zh-CN / zh-HK).
The Rust / Python / Node.js / C++ examples in the same page were already
correct.
The accompanying runtime crash (`JNI call failed`) is fixed separately
in longbridge/openapi#587.
copy-routes now emits both foo.html and foo/index.html so the unchanged nginx serves docs/sdk (.html) and the catch-all (/index.html). Fix an invalid empty selector in api-reference.css that broke lightningcss minify. Exclude migration URLs from the sitemap. Point the release/canary deploy at dist/ instead of the removed docs/.vitepress/dist (was uploading an empty dir → no md/html on OSS).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Faithful React+SVG port of the legacy QuantChart.vue (macd/rsi/bb/ema/stoch) with the baked-in sample series; renders SSR-safe. Re-point the MDX map to it and drop the placeholder stub.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the legacy config.mts head layer + theme/index.ts enhanceApp side effects that the migration dropped: apiProxyBootstrap + longport-internal login bridge, Google Analytics, __LB_PROXY__, 神策 sensors SDK + init (pageview/click heatmap/stay/member binding), iconfont, Google One-Tap, and Helora support-widget boot. Add saveInviteCodeFromUrl (cookie capture on load + navigation) and a route progress bar (site-init). Whale-app (in-app WebView): UA detection hides the top nav / local nav / footer and takes the theme from lbtheme/<mode> (no-flash inline + ThemeToggle guard). Per-page SEO head: canonical + hreflang alternates + og/twitter + text/markdown alternate. CN region hides the Pricing/Docs nav items and the Features menu. Also render mermaid fences client-side and restore the theme-toggle circular view-transition.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Header is now position:fixed (like legacy) so it stays pinned during overscroll; #main-content reserves its 60px. Rebuild the mobile top nav (icon hamburger, accordion Features, overlay panel) and the docs local-nav (reveal-on-scroll bar + On this page TOC). Fixed-header/mobile fixes: 60px logo→menu gap, mobile tail alignment, footer 2-col + centered bottom bar, breadcrumb/doc-footer alignment, symmetric mobile padding. MCP page excluded from the docs sidebar (sidebar:false). Migrate the login redirect logic from the legacy AppNav: createLoginRedirectPath (/login?redirect_to=<sso?redirect_to=current>&logout=1&with-us=1), login state via window.longportInternal, and the logged-in Dashboard + avatar dropdown; UserAvatar now renders the full login block.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port the legacy VitePress homepage (10 sections: hero, channels, features, CLI spotlight, AI Skill, MCP, API capabilities, SDK, get-started, CTA) and the Skill page (hero, demo, catalog, capability, cases, get-started) as hydrated React islands, matching legacy markup/CSS and interactions. Add a shared CopyButton with copied feedback across the homepage code blocks. Remove 14 orphaned homepage sections and the old SkillCatalog; wire the home/skill islands via the [...slug] routes and the mdx-component map.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Docs pages render a slim link-row footer (DocFooter) instead of the big
marketing footer; BaseLayout gains a hideFooter prop
- Callout blocks restyled to the legacy TipContainer look (1px border, radius,
per-type icon, accent-coloured title + body)
- Doc meta row (Markdown / Edit / Last Updated) via DocMeta; real per-page
dates from origin/main git history (src/lib/last-updated.ts)
- Body copy: antialiased smoothing + faint rgba(0,0,0,.06) underline colour to
match legacy .vp-doc
- Heading anchors moved to the left gutter; scroll-margin-top offsets the 61px
sticky header on TOC/anchor jumps
- TOC pinned at its at-rest offset so it no longer jumps up on scroll
- Breadcrumb: bold current-page crumb
- Sidebar: childless category folders (e.g. cli/ipo) render as clickable bold
headers instead of dead muted leaves
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Legacy `.vp-doc strong` is 600. Our bold text used the browser/Preflight
default (bolder → 700). Added `.docs-content strong, b { font-weight: 600 }`.
Verified: **Quote** etc. now compute 600.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Boss noticed the article font differed. Legacy self-hosts Inter (woff2) and
renders `.vp-doc` content in Inter 15px / ~1.75 line-height — a different
face from the SF Pro Display used by the site chrome (nav/marketing). Our
Astro build used SF Pro Display for everything, so the doc body was the
wrong typeface + size (16px).
- Added `@fontsource-variable/inter` (self-hosted variable font, same
approach as legacy — no external Google Fonts request).
- Imported it in global.css.
- `.docs-content` now uses `"Inter Variable", Inter, ui-sans-serif,
system-ui, …` at 15px / 1.75 line-height (headings inherit Inter; code and
tables keep their mono / 14px). Site chrome keeps SF Pro Display.
Verified: Inter Variable loads (document.fonts.check true); .docs-content p
= Inter 15px, h1 = Inter 32px; nav still SF Pro Display.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Boss: table (and generally the article) text looked blacker than legacy.
Root cause is global, not table-specific: legacy `.vp-doc` colors all
content — paragraphs, table cells, headings — with --vp-c-text-1 = #3c3c43
(a soft near-black). Our content inherited --lb-fg-1 (#0A0E19, near pure
black), so everything read darker.
Scoped the softer tone to `.docs-content`:
- `.docs-content { color: #3c3c43 }` (light) / `rgba(255,255,245,0.86)`
(dark), matching legacy --vp-c-text-1.
- Headings and `td` changed to `color: inherit` (were pinned to the darker
--lbus-c-text / --lb-fg-1) so they pick up the softer body color.
- `th` keeps the muted --lb-fg-2 (matches legacy header rgb(103,103,108)).
Site chrome (nav/sidebar) is untouched — it still uses the --lb-* tokens.
Verified on /docs/quote/objects: p / td / h1 / h2 all rgb(60,60,67) =
legacy #3c3c43; th rgb(108,110,117).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First pass of the content-style parity work (code blocks + tables — the two
widest-reach elements: 267 and ~300 pages). Values measured from legacy
production `.vp-doc` via getComputedStyle.
Code blocks (`.docs-content pre`, shiki `pre.astro-code`):
- radius 6→8, padding 12/16→20/24, font 16→13px, margin 16/0 — matching
legacy. Shiki emits an inline white bg for github-light; overridden to
--lb-bg-2 (≈ legacy #f6f6f7) via `html:not([data-mode=dark])` so it only
affects light — the dark bg (#24292e) keeps flowing through shiki.css's
existing `[data-mode=dark] .astro-code` swap. Inline `code` now gets the
legacy chip style (bg --lb-bg-2, radius 4, 0.875em, JetBrains Mono).
Tables (previously UNSTYLED — Tailwind Preflight had stripped all borders):
- block + overflow-x auto (horizontal scroll), collapsed 1px dividers in
--lbus-c-divider (= legacy rgba(0,0,0,.06)), header + even-row zebra in
--lb-bg-2, 8/16 cell padding, header 600-weight muted (--lb-fg-2), body
cells --lb-fg-1 — matching legacy.
Also replaces the previously-broken references to the undefined tokens
--lbus-c-bg-soft / --lbus-c-border in the old pre/code rules with real
--lb-* tokens.
Verified light + dark on /docs/quote/objects: code bg #f3f5f6 (light) /
#24292e (dark); table borders rgba(0,0,0,.06) (light) / rgba(255,255,255,
.06) (dark); header + zebra --lb-bg-2 both modes.
Remaining content elements (links, blockquote, images, callouts, nested
lists, hr) still use plain/undefined styling — next passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Boss: the article H1 (e.g. "Overview") was missing. Legacy renders the
page title as the H1 at the top of the article. The docs convention splits:
155 pages start with their own body `#` heading (VitePress shows that), but
pages like index.mdx and quote/objects.mdx have no body `#` — their
frontmatter title becomes the H1. Our DocsLayout rendered neither an
injected title nor (for those pages) any H1, so the title vanished.
DocsLayout now injects `<h1>{entry.data.title}</h1>` after the breadcrumb
ONLY when the body has no level-1 heading (`headings.some(depth === 1)` is
false). Verified no body H1 uses the `{#id}` syntax, so the headings array
is reliable for the check and there's no risk of a double H1.
Verified:
- /docs → H1 "Overview" (injected)
- /docs/quote/objects → H1 "Definition" (injected)
- /docs/quote/overview → H1 "Quote API Overview" (its own body H1; the
title "Overview" is NOT injected — no duplicate)
The injected H1 sits inside .docs-content (picks up the 2rem article inset
and the 2rem font-size); the DOM-derived TOC only scans h2/h3 so it is
unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Boss: the left sidebar menu should line up with the header logo. The
header is a centered `max-w-[1240px] px-6 mx-auto` container (logo at
container-left + 24px), but the full-width docs layout put the sidebar
flush at the viewport edge (items at ~16px) — misaligned by ~108px at
1440.
- `.docs-layout` (lg) gains `padding-left: max(0px, calc((100vw - 1240px)
/ 2))`, matching the header container's left edge, so the sidebar column
starts where the header content does.
- Sidebar aside padding `px-4` → `px-6`, matching the header's px-6, so the
item pill's left edge lands at container-left + 24 = the logo.
Legacy does the same (sidebar link left == logo left, both 404 at 2000px).
Verified at 1440: sidebar pill left 124 == logo left 124 (delta 0); item
icon 16px right, mirroring legacy. Content region still fills to the right
(wide article) and the wash stays scoped to the content region.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two boss findings:
1. The top wash bled under the sidebar/menu column.
2. On wide screens the whole layout was capped at 80rem and centered in the
viewport, so the article got squeezed into the middle with big empty
margins on both sides — legacy keeps the sidebar flush-left and lets the
content region fill the rest.
Restructured to the legacy two-level model:
- `.docs-layout` is now a full-width grid: `16rem minmax(0,1fr)` (sidebar |
content region). No more centered 80rem block.
- New `.docs-body` wraps the content region; the wash moved to
`.docs-body::before` so it covers only the area right of the sidebar,
never under the menu.
- New `.docs-inner` caps the article + TOC at 80rem and centers them within
the post-sidebar space (the legacy `.VPDoc .container` behavior): article
fills, TOC 14rem, 4rem gutter between them.
- Article/breadcrumb offsets preserved (body padding-left 2rem, breadcrumb
margin-left -2rem → breadcrumb hangs 32px left of the body).
DocsLayout.astro wraps <article> + <TOC> in `.docs-body > .docs-inner`.
Verified at 1440px: sidebar 0–256 (flush left), content region 256–1440,
article text ~800px wide (was ~640, squished), left/right gutters 64px,
wash present on the content region only (sidebar column stays white). On
wider viewports the inner caps at 1280 and centers in the post-sidebar
space, matching legacy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Boss review against legacy production (measured at 1440px):
- article gutters were 32px each side vs legacy 64px
- breadcrumb sat 21px RIGHT of the body text; legacy has it ~32px LEFT
- TOC "On this page" was flush at the top (65px); legacy sits ~108px, level
with the article body, not the breadcrumb
Changes to docs.css:
- `.docs-content` gets `padding: 0 2rem`. Combined with the grid's 2rem gap
this restores ~64px gutters between the article and the sidebar / TOC.
- The breadcrumb gets `margin: 0 -2rem`, pulling it back out of that padding
to the column's left edge — so it hangs 32px left of the indented body,
matching legacy.
- `.docs-toc` uses `margin-top: 2.75rem` (not padding) to drop "On this
page" down level with the article start while still sticking near the top
on scroll; the divider border moves to the inner `nav` so the line begins
at the title rather than in the margin gap above it.
Verified at 1440px: left gutter 64, right gutter 64, breadcrumb 32px left
of body, On-this-page top 105 (legacy 108).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two docs-page polish items from boss review:
- **TOC divider** — the "On this page" column had no separator from the
article. Legacy production draws it on `.VPDocAsideOutline .content` as
`border-left: 1px solid rgba(0,0,0,.06)` — exactly our --lbus-c-divider.
Added `border-left` + `padding-left: 1.5rem` to `.docs-toc`.
- **Top wash (晕染)** — a subtle brand-tinted radial gradient at the top of
the docs page, echoing the homepage hero. Implemented as
`.docs-layout::before` (absolute, out of the grid flow; grid children
lifted with z-index) — `radial-gradient(ellipse 90% 60% at 50% 0%,
color-mix(--lb-brand 12%, transparent), transparent 72%)`, 420px tall.
NOTE: legacy docs pages have no such wash (verified against production),
so this is a new effect per request — intensity (currently 12%) is easy
to dial.
Verified on /docs: TOC shows the left divider; a gentle mint glow sits
behind the top of the content.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Boss: the right-hand "On this page" was missing items (Rate Limit,
Pricing) vs legacy. Those two headings use the vitepress custom-id syntax
`## Rate Limit {#rate-limit}`. The vite preflight in astro.config.ts
(step 2) rewrites `## Foo {#bar}` into a raw JSX `<h2 id="bar">…</h2>`
element before the markdown pipeline runs (MDX/acorn chokes on the `{…}`
otherwise). Astro's heading collector walks the mdast for `heading`
nodes, so JSX-element headings never enter the `headings` export — they
render on the page but vanish from the TOC.
Derive the TOC from the rendered DOM instead of trusting the server
`headings` prop: on mount (and on each astro:page-load) scan
`.docs-content h2, h3` for elements with an id. This captures every
rendered heading regardless of how it was produced (markdown, JSX-rewrite,
or component). The prop still seeds the SSR render so there's server HTML;
the DOM reconciles the full set after hydration.
Verified: /docs TOC now lists all 7 sections including Rate Limit and
Pricing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Boss: switching docs pages did a full document reload — white flash and
visible jank — whereas the legacy VitePress site navigated client-side.
Astro is MPA by default; each nav reloaded the whole page.
Add Astro's ClientRouter (View Transitions): same-origin navigations are
now intercepted and the document is swapped client-side (no white flash,
smooth fade). Three cross-cutting caveats handled:
- **Theme flash** — a swap replaces <html>'s attributes with the new
document's SSR default (data-mode "light"), so a dark-mode user would
flash light on every nav. The head theme script now defines applyTheme()
and registers it on `astro:after-swap` (fires before paint); the listener
lives on `document` so it is not duplicated per swap. Verified: dark stays
dark across client navs (body bg #0A0E19, no flash).
- **Shell persistence** — TopNav & Footer get `transition:persist` keyed by
locale (`nav-${locale}` / `footer-${locale}`): the shell stays mounted
within a locale (no re-hydrate flicker, theme-toggle keeps its state) but
re-mounts when the locale changes so labels/links update. Because the
persisted TopNav's `pathname` prop goes stale, TopNav now tracks the path
in state and refreshes it on `astro:page-load` (also closes the mobile
menu) — active highlight + LanguageSwitcher stay correct.
- **Per-page script re-run** — tabs-hydrate ran once on DOMContentLoaded, so
Tabs on client-swapped pages were never wired up. Switched to
`astro:page-load` (fires on initial load + every swap) with a
`data-tabsHydrated` idempotency guard. Verified: 7-tab page hydrates after
a client nav.
Sidebar/TOC are intentionally NOT persisted so their active state + scope
(docs vs cli) recompute per page; they swap client-side (no white flash).
Verified in browser: a window sentinel survives navigation (client swap,
not full reload); URL updates; sidebar + top-nav active states update; dark
theme persists; Tabs hydrate; console clean (no hydration errors).
KNOWN FOLLOW-UP: the non-persisted sidebar resets its scroll position on
each nav (legacy preserved it). Persisting it needs after-swap active/
auto-expand/scope re-sync — deferred to a separate change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Boss: legacy has no gap between the header and the first sidebar item /
breadcrumb. `.docs-layout` had `padding: 2rem 1rem`, and since the header
is sticky (in flow, 60px) the 2rem (32px) top padding pushed the whole
grid down — leaving a full-width empty band between the nav and the first
sidebar row / breadcrumb.
Dropped the top padding (`padding: 0 1rem 2rem`). Columns now sit flush
under the header with only their own small insets (sidebar py-6 = 24px,
breadcrumb ~14px, TOC flush). Measured: header bottom 61 → first sidebar
item 85, breadcrumb 75, TOC 61.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Switching pages threw "Hydration failed because the server rendered HTML
didn't match the client" pointing at ThemeToggle's icon. `isDark` called
matchMedia('(prefers-color-scheme: dark)') during render: on the server
(and thus in the SSR HTML) window is undefined so isDark was false →
MoonIcon; on a dark-preferring machine the client's first render (which
must byte-match the server) evaluated matchMedia → true → SunIcon, so the
trees diverged and React regenerated the island on every navigation.
Gate the theme-dependent icon behind a `mounted` flag (false during SSR
and the first client render, set true in useEffect). Both sides now render
MoonIcon initially — matching HTML — then the effect resolves the real
theme and swaps to the correct icon. Light-mode users see no change;
dark-mode users get a one-frame Moon→Sun swap after mount.
Verified: navigating /docs/getting-started → /docs/llm shows a clean
console (no hydration error), only the benign vite + buffer-externalized
notices.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After adding the root "Overview" (/docs) entry, isActiveNode matched a
link with `pathname === link || pathname.startsWith(link + '/')`. Since
"/docs" is a path prefix of every docs page, "/docs/getting-started"
satisfied `startsWith("/docs/")`, so BOTH Overview and Getting Started
rendered the active teal pill.
A link is active only on its exact page; the prefix branch is removed.
Group activeness is still derived from descendants (node.items.some),
which is unaffected — Quote/Stocks still auto-expand on a nested active
page.
Verified: /docs/getting-started now highlights only "Getting Started";
Overview is inactive. Each of the two sidebar mounts (desktop sticky +
mobile drawer) has exactly one active row.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to the sidebar refactor. The legacy sidebar shows a top-level
"Overview" (/docs) as the first, active item — it's docs/{locale}/docs/
index.mdx (title Overview / 平台介绍 / 平台介紹, sidebar_position -999,
book_open icon). buildItems() skipped EVERY index.mdx, so that page never
appeared in the tree and /docs had no active sidebar item.
buildItems() gains an `includeIndex` param (default false). buildSidebar's
root call passes true so the root index.mdx is emitted as a top-level leaf
(link /docs, /zh-CN/docs, /zh-HK/docs via urlFromAbsPath; -999 sorts it
first). Nested index.mdx files still skip — they're absorbed as directory
links via _category_.json, unchanged.
Applies to both scopes: docs/{locale}/docs/index.mdx and the cli sidebar's
docs/{locale}/docs/cli/index.mdx (both exist in all three locales).
Verified: /docs now shows "Overview" with book-open icon + teal active
pill at the top of the first group, matching the legacy reference.
navigation.test.ts 3/3 pass (non-empty, zh-CN count == en count, position
ascending all still hold — the new node's -999 sorts first).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Boss reference (image #27, legacy master): the astro sidebar (image #26)
was a flat gray list — no group dividers, no bold section headers, icons
never rendered, collapse non-interactive, wrong active state. Rebuilt to
reproduce the legacy VPSidebar look.
- **`sidebar-icons.ts`** (new) — the 22 lucide 16×16 SVG fragments copied
verbatim from legacy docs/.vitepress/theme/utils/gen.ts SIDEBAR_ICONS,
keyed by the `sidebar_icon` frontmatter / `_category_.json` icon value.
Fragments use stroke=currentColor so the row's text color drives them.
- **`SidebarItem.tsx`** — was rendering icons as an empty `data-icon` span
(glyphs never appeared) and the group `<button>` had no onClick (collapse
was dead). Rewritten:
· Leaf link: rounded-lg px-4 py-1, optional icon (fg-3 / brand when
active), active = teal text + `color-mix(in oklab, --lb-brand 10%,
transparent)` pill (matches legacy `.is-active .VPLink`), hover =
brand text + bg-2.
· Group: interactive collapse via useState seeded from active/collapsed.
level-0 header is bold --lb-fg-1 with icon; deeper sub-section headers
are medium --lb-fg-2. Caret is a chevron that points right when
collapsed and rotates 90° to point down when open (legacy .caret-icon).
· Active group auto-expands (open seeds true when a descendant matches).
- **`Sidebar.tsx`** — coalesces the flat top-level node list into groups:
consecutive leaf links bundle into one header-less block, each section
node stands alone. Groups after the first get a top divider
(border-t --app-card-stroke + 10px gap), reproducing legacy `.group +
.group` separators.
Verified in browser: /docs shows grouped sidebar with dividers, bold
Quote/Fundamental/Market headers + bar-chart/book-open/line-chart icons +
down-chevrons; /docs/quote/objects shows "Definition" with the teal active
pill and Quote auto-expanded — matching the legacy reference.
KNOWN GAP (data layer, not touched here): the root "Overview" (/docs,
docs/en/docs/index.mdx, sidebar_position -999) is absent from the top of
the first group because buildItems() in navigation.ts skips every
index.mdx. Legacy shows it as the first, active item. Flagged for a
separate data-layer fix pending confirmation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Boss test: dev environment showed "Search is available after first
production build" — pagefind only generates its index during
`bun run build`, which was flagged as a broken user experience.
Swap the whole search substrate to build-time JSON index +
client-side minisearch:
- **`src/pages/search-index.[locale].json.ts`** (new) — Astro dynamic
route emitting `/search-index.{en,zh-CN,zh-HK}.json`. Each doc is
sliced into sections (one per heading), each carrying the ancestor
heading breadcrumb (h1 → h2 → h3) so the client can render the
legacy `# Board - Security Board` / `# Scene Demonstration >
Submit Order` layout without a second round-trip. Body text is
stripped of MDX / code fences / markdown syntax and capped at 2000
chars per section. Runs during both `astro dev` and `astro build`
— dev serves it live, so the search works from the first click.
Region-filtered via `includedInRegion` so CN builds get the CN
sitemap.
- **`SearchDialog.tsx`** — pagefind removed; minisearch@7 added.
Per-locale index cached in a module-level `Map` so subsequent opens
are instant; first open pays one fetch + `addAll`. Custom
`tokenize` splits CJK into per-character tokens while keeping
western words whole — needed because minisearch's default whitespace
tokenizer produces zero tokens for `实时行情报价`. Fields boosted
`title:3, headings:2, body:1`, `prefix + fuzzy 0.15`. Debounce
effect re-fires on `status` change so a query typed while the
index is still loading gets served the moment it's ready. Dialog
chrome updated to match legacy image #25 — search icon, clear
button, keyboard hint bar (↑↓ Switch · ↵ Select · esc Close), and
the same `--app-mono` kbd styling as the ⌘K badge.
- **`SearchResults.tsx`** — new UI:
# <h1 title> > <h2 title> > <h3 title>
with matched query terms wrapped in a teal `<mark>` (against
`--lb-fg-invert`), and the deepest heading rendered bold. Selected
row gets a teal border + inset ring — same active-state affordance
as the legacy screenshot.
- **De-dup guard** — the section builder occasionally emits two
sections with identical `id` when a doc reuses a heading text at
the same depth (e.g. two `## Examples` blocks); MiniSearch throws
on duplicate adds. Filtered client-side before `addAll`.
- **extractField gotcha** — MiniSearch v7's `extractField` is used
for BOTH tokenization AND `storeFields`. Naively stringifying every
field turns the `headings: string[]` array into "a,b,c" in the
stored result, and the UI's `hit.headings.map(...)` crashed with
"map is not a function" — the dialog disappeared to a blank
fallback. Fixed by returning raw values for non-virtual fields and
only stringifying the synthetic `headingsJoined` for tokenization.
Verified in Chrome DevTools: dev-served /search-index.en.json returns
2698 sections (1.1 MB), first-open build takes ~800 ms, typing
"security" returns 12 hits including "# Security News",
"# longbridge security-list › Examples", "# longbridge security-list
› Examples › List securities by market" — exactly the legacy shape.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Boss's screenshot: clicking the search input opened a dialog that was
confined to the top nav bar strip, not a global overlay. The backdrop
also did not cover the page.
Root cause is a subtle CSS containing-block rule: any ancestor with
`filter`, `backdrop-filter`, `transform`, `perspective`, `will-change`
or `contain` becomes the containing block for `position: fixed`
descendants — so those descendants are NOT positioned relative to
the viewport but to that ancestor's box.
TopNav.tsx sets `[backdrop-filter:saturate(180%)_blur(20px)]` on the
<header> (added last round for legacy parity). SearchDialog was
rendered as a child of SearchButton, which lives inside <header>, so
its `fixed inset-0` backdrop resolved to "cover the 60px header" — not
"cover the viewport."
Fix: React portal SearchDialog to `document.body`, escaping the
backdrop-filter ancestor. Guarded with a `useEffect` that captures
`document.body` after mount so SSR does not touch `document`.
Behavior otherwise unchanged.
Verified: click search → global backdrop covers full viewport, dialog
centered at top:pt-20, everything else darkened.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Boss's screenshot: on /docs pages the left sidebar stretched from
top:0 to bottom, painting over the entire nav bar (Features / Pricing
/ Skill / CLI / MCP / Docs). Root cause was a dead-CSS + wrong-utility
combination:
- `src/styles/docs.css` defined `.docs-sidebar { position: sticky;
top: 3.5rem; ...}` and `.docs-toc { top: 3.5rem; ...}`, but neither
Sidebar.tsx nor TOC.tsx ever applied those classNames. The rules
were dead code.
- Sidebar.tsx used `fixed inset-y-0 left-0 z-30 w-64 ... lg:relative
lg:translate-x-0`. On lg+ the `lg:relative` won for `position`, but
`inset-y-0` (top:0 + bottom:0) stayed active — stretching the aside
from viewport top down over the sticky 60px header.
Fix:
- **`Sidebar.tsx`** — className rewritten so the two viewports carry
their own contract:
Mobile: fixed inset-y-0 left-0 z-40 w-64 (full-height drawer,
z-40 so it covers the sticky header when the mobile menu opens,
matching legacy AppSidebar.vue).
Desktop (lg+): sticky top-[60px] inset-y-auto h-[calc(100vh-60px)]
z-auto translate-x-0 (a column that begins BELOW the header and
never reaches it). Added py-6 px-4 to restore the legacy sidebar
padding.
- **`LocalNav.tsx`** — was `sticky top-14` (56px = old h-14 header
guess). Bumped to `top-[60px]` so mobile local-nav sits flush under
the 60px header instead of leaving a 4px gap.
- **`TOC.tsx`** — added the `docs-toc` className so the CSS ruleset
in docs.css actually takes effect. Also bumped its `top: 3.5rem` /
`max-height: calc(100vh - 3.5rem)` values in docs.css to 60px to
match the header.
- **`docs.css`** — dead `.docs-sidebar` sticky ruleset removed. The
sidebar's positioning now lives inline in Sidebar.tsx where the
Tailwind utilities can't be silently overridden by an unused CSS
block.
Verified: navigating to /docs shows the left sidebar starting under
"Home / Overview" breadcrumb (below the 60px header), and the top
nav — Features / Pricing / Skill / CLI / MCP / Docs — is fully
visible. TOC (ON THIS PAGE) also starts under the header on the right.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Boss called out: Features dropdown was a stub — clicking did nothing.
Ported the full nine-tile dropdown from legacy.
- **`src/data/features-menu.ts`** — verbatim data copy from legacy
FeaturesMenu.vue lines 14-155: the same nine tiles (Live Market
Quotes, Price History, Financial Statements, Analyst Estimates,
Company News, SEC Filings, Institutional, Options & Warrants, Order
Execution) with lucide-style icon path fragments, deep links into
the /docs/ tree, and the en / zh-CN / zh-HK translations kept in
lockstep. `localePath(locale, path)` mirrors legacy `useLocalePath`.
- **`src/components/shell/FeaturesMenu.tsx`** — React port with the
same behavior contract as the vue original: mouse-enter opens the
panel, mouse-leave closes it on a 150ms timer (matches legacy
cursor-gap tolerance so users can traverse to the panel without it
vanishing), click toggles for keyboard/tap parity, outside-click and
Escape dismiss. Icons rendered via `dangerouslySetInnerHTML` inside
a lucide 24×24 viewBox — same technique legacy used with `v-html`.
Panel: `w-[640px]`, `grid-cols-3 gap-1`, `rounded-xl border p-2` with
`--lb-shadow-menu`. Tokens migrated from `--vp-*` → `--lb-*` /
`--app-*` (semantic aliases). Trigger reuses the exact nav-link
spec (13.5px / 500 / 6·12 · fg-1@78%) and turns teal + rotates
chevron 180° when open, matching legacy `.is-active` state.
- **`TopNav.tsx`** — swap the Features stub button for
`<FeaturesMenu locale={locale} />`. Shares the same `client:load`
hydration boundary as the surrounding TopNav island (Astro hydrates
the whole React subtree, no extra directive needed).
Verified via chrome-devtools: click flips aria-expanded to "true",
menu with 9 menuitem children renders, screenshot matches boss's
reference image #21 tile-for-tile (icon, title, desc, layout).
NOTE: this session's dev server needed a manual `rm -rf
node_modules/.vite` + restart to clear a "504 Outdated Optimize Dep"
that Astro's HMR would not resolve on its own once a new file entered
the module graph — recorded here so the next surprise hydrate failure
after adding a component knows what to try first.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>