Refactor/cleanup soc#1
Conversation
Signed-off-by: Thiago Moura <thiagogcm@gmail.com>
Signed-off-by: Thiago Moura <thiagogcm@gmail.com>
There was a problem hiding this comment.
Pull request overview
This PR refactors the site’s client-side behavior and import structure by introducing a @/ alias, extracting several inline .astro scripts into dedicated src/scripts/* modules guarded by an initOnce() helper, and adding a first-class in-memory search implementation (prebuilt index + client UI). It also standardizes formatting/linting via Biome and tightens dependency version pinning.
Changes:
- Add TypeScript path alias (
@/* → src/*) and update imports across pages/components/scripts to use it. - Extract inline UI behaviors (TOC tracking, theme toggle, search modal, header scroll, mermaid enhancements, etc.) into reusable
src/scripts/*modules with a sharedinitOnce()guard. - Introduce a shared search library (
src/lib/search.ts) + server index route (/search.json) + client-side search modal behavior, including new Node test coverage.
Reviewed changes
Copilot reviewed 60 out of 62 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.json | Add @/ path alias + TS types |
| src/worker.ts | Use alias import + formatting |
| src/scripts/toc.ts | New TOC scroll/hash syncing |
| src/scripts/theme-toggle.ts | New theme toggle behavior |
| src/scripts/search.ts | New client search behavior |
| src/scripts/mobile-docs-nav.ts | New mobile docs nav behavior |
| src/scripts/mermaid.ts | New mermaid enhancement module |
| src/scripts/init-once.ts | New once-per-session initializer |
| src/scripts/heading-permalinks.ts | New heading permalink behavior |
| src/scripts/header-scroll.ts | New sticky-header scroll behavior |
| src/scripts/adf4j-demo.ts | Import cleanup + formatting |
| src/scripts/activity-chart.ts | Use shared activity type |
| src/pages/search.json.ts | Build search index via shared lib |
| src/pages/projects/index.astro | Alias imports + remove unused UI |
| src/pages/projects/adf4j/demo.astro | Alias imports cleanup |
| src/pages/projects/[project]/[...page].astro | Alias imports cleanup |
| src/pages/projects/[id].astro | Alias imports cleanup |
| src/pages/index.astro | Hero copy/layout tweaks + alias |
| src/pages/blog/index.astro | Alias imports cleanup |
| src/pages/blog/[id].astro | Alias imports cleanup |
| src/pages/api/confluence-adf.json.ts | Alias imports + formatting |
| src/pages/about.astro | Alias imports cleanup |
| src/pages/404.astro | Alias imports cleanup |
| src/lib/site-owned-pages.ts | Formatting/string normalization |
| src/lib/search.ts | New search scoring/snippets lib |
| src/lib/search.test.ts | New tests for search lib |
| src/lib/project-actions.ts | Alias import cleanup |
| src/lib/nav.ts | Simplify nav helpers |
| src/lib/markdown-preview.ts | Import ordering/formatting |
| src/lib/html.ts | New shared HTML escaping helper |
| src/lib/github-stats.ts | Formatting + small refactors |
| src/lib/github-stats.test.ts | Formatting/quote normalization |
| src/lib/format-date.ts | Formatting/quote normalization |
| src/lib/confluence.ts | Formatting/quote normalization |
| src/lib/collections.ts | Formatting + alias import |
| src/layouts/DocsLayout.astro | Replace inline script with module |
| src/layouts/BaseLayout.astro | Replace inline script with module |
| src/content/projects/website.md | Update tags metadata |
| src/content/blog/code-is-cheap-verification-is-not.md | Copy edits + mermaid text tweak |
| src/content.config.ts | Formatting/quote normalization |
| src/consts.ts | Formatting + minor structure tweaks |
| src/components/ThemeToggle.astro | Replace inline script with module |
| src/components/Tabs.astro | Alias imports cleanup |
| src/components/TableOfContents.astro | Replace inline script with module |
| src/components/StatusIsland.astro | Alias imports + formatting |
| src/components/SearchModal.astro | Replace inline script with module |
| src/components/ProjectPageHeader.astro | Alias import cleanup |
| src/components/ProjectDocsNav.astro | Use isActive helper |
| src/components/MobileDocsNav.astro | Replace inline script with module |
| src/components/MetricPanel.astro | Alias imports cleanup |
| src/components/MermaidDiagrams.astro | Replace inline script with module |
| src/components/Icon.astro | Formatting/quote normalization |
| src/components/Header.astro | Alias imports cleanup |
| src/components/Footer.astro | Alias imports cleanup |
| src/components/DirectoryEntry.astro | Alias imports cleanup |
| src/components/Adf4jDemo.astro | Alias import cleanup |
| README.md | Update example + table formatting |
| package.json | Add Biome + add test:lib |
| package-lock.json | Lockfile updates for deps |
| content-sources.yaml | Remove trailing blank entry |
| biome.json | New Biome config |
| astro.config.mjs | Formatting/quote normalization |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Trailing headings on a short page can't reach the line; pull out-of-reach | ||
| // thresholds back into range (from the end), kept ordered so each gets a turn. | ||
| const n = natural.length; | ||
| const gap = Math.max( | ||
| 1, | ||
| Math.min(0.25 * window.innerHeight, maxScrollTop / n), | ||
| ); | ||
| const out = natural.slice(); | ||
| out[n - 1] = Math.min(out[n - 1], maxScrollTop); | ||
| for (let i = n - 2; i >= 0; i--) { | ||
| out[i] = Math.min(out[i], out[i + 1] - gap); | ||
| } | ||
| thresholds = out; |
| const toolbar = document.createElement("figcaption"); | ||
| toolbar.className = "mermaid-diagram__toolbar"; | ||
| toolbar.setAttribute("role", "tablist"); | ||
| toolbar.setAttribute("aria-label", "Mermaid diagram view"); | ||
| toolbar.innerHTML = ` | ||
| <button class="mermaid-diagram__tab" type="button" role="tab" data-mermaid-view="diagram" aria-selected="true">Diagram</button> | ||
| <button class="mermaid-diagram__tab" type="button" role="tab" data-mermaid-view="code" aria-selected="false">Code</button> | ||
| `; | ||
|
|
||
| const diagramPanel = document.createElement("div"); | ||
| diagramPanel.className = | ||
| "mermaid-diagram__panel mermaid-diagram__panel--diagram"; | ||
| diagramPanel.dataset.mermaidPanel = "diagram"; | ||
| diagramPanel.setAttribute("role", "tabpanel"); | ||
| diagramPanel.innerHTML = ` | ||
| <div class="mermaid-diagram__canvas" aria-label="Rendered Mermaid diagram"></div> | ||
| <p class="mermaid-diagram__status" role="status">Rendering diagram</p> | ||
| `; | ||
|
|
||
| const codePanel = document.createElement("div"); | ||
| codePanel.className = "mermaid-diagram__panel mermaid-diagram__panel--code"; | ||
| codePanel.dataset.mermaidPanel = "code"; | ||
| codePanel.setAttribute("role", "tabpanel"); | ||
| codePanel.hidden = true; |
- toc: drop the 1px gap floor and skip remapping on non-scrollable pages so threshold remapping can't push earlier thresholds negative and mark a later heading active at the top of the page - mermaid: give each diagram unique ids and wire aria-controls/aria-labelledby between tabs and panels so assistive tech announces the relationship Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016SPYm9CnBdi9BzuUfjW7Xt
| // `toggle` doesn't bubble, so listen in the capture phase. | ||
| let openPanel: HTMLDetailsElement | null = null; | ||
| let openedAtY = 0; |
| // Prebuilt per-term regexes (built once per render) wrap matches in <mark>. | ||
| const highlight = (text: string, regexes: RegExp[]) => { | ||
| let out = escapeHtml(text); | ||
| for (const re of regexes) { | ||
| out = out.replace(re, "<mark>$1</mark>"); |
- mobile-docs-nav: reset the cached openPanel/openedAtY on astro:after-swap so handlers don't act on (or pin) a <details> detached by a view transition - search: highlight against the raw text with one combined matcher, escaping only around matches, so a later term can't match markup/entities an earlier term inserted (e.g. "code a" no longer produces broken HTML) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016SPYm9CnBdi9BzuUfjW7Xt
| for (const match of text.matchAll(matcher)) { | ||
| out += `${escapeHtml(text.slice(last, match.index))}<mark>${escapeHtml(match[0])}</mark>`; | ||
| last = match.index + match[0].length; | ||
| } |
There was a problem hiding this comment.
The premise here isn't accurate for this code. The optional-index concern applies to RegExpMatchArray (what String.prototype.match() returns without the global flag). But text.matchAll(matcher) yields RegExpExecArray, where index: number and [0]: string are both required, so no guard is needed and the code type-checks under strict.
Two pieces of evidence:
-
astro check— the project's strict checker (extends: astro/tsconfigs/strict,lib: ["ESNext", "DOM", "DOM.Iterable"]) — reports 0 errors / 0 warnings, including after this exact code landed. -
An isolated probe under the same settings compiles cleanly:
for (const match of text.matchAll(/l/gi)) { const i: number = match.index; // ok — required on RegExpExecArray const s: string = match[0]; // ok — required }
npx tsc --noEmit --strict --target ESNext --lib ESNext,DOM,DOM.Iterablepasses.
So adding a guard would be unreachable code; leaving as-is.
No description provided.