Skip to content

Refresh docs landing page hero and header#233

Merged
eluce2 merged 12 commits intomainfrom
feature/docs-landing-hero-header
May 4, 2026
Merged

Refresh docs landing page hero and header#233
eluce2 merged 12 commits intomainfrom
feature/docs-landing-hero-header

Conversation

@eluce2
Copy link
Copy Markdown
Collaborator

@eluce2 eluce2 commented May 4, 2026

Summary by CodeRabbit

  • New Features

    • New marketing pages, interactive examples, animated visuals, and a download UI/endpoint for platform installers.
  • Documentation

    • Major ProofKit AI docs added (getting started, build/deploy, agent workflow, data access, troubleshooting, platform notes).
    • Docs reorganized with an AI section and updated navigation.
    • Terminology standardized to “Web Viewer” across docs and UI.
  • Chores

    • Updated site config, registries, package metadata, ignore rules, and localized strings.

toddgeist and others added 11 commits May 1, 2026 08:06
…AI, add FM Bridge ref, /download route

- combined hybrid-apps + webviewer sections into single Web Viewer Apps
- moved cli/webviewer pages (getting-started, deployment-methods, overview→faq) into webviewer
- renamed AI section to ProofKit AI w/ Sparkles icon, tagline tightened
- theme-aware logo in nav header
- new FM Bridge reference doc w/ MCP connector callout
- /download/[platform]/[version] route w/ zod validation, manifest fetch, asset selection
- redirects for old /docs/hybrid-apps + /docs/cli/webviewer paths

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- new DownloadButton component (client) detects mac/win from UA
- primary action = detected OS, dropdown shows the other
- replaces duplicate mac/win Link pairs on home, why-proofkit, examples
- adds shadcn button-group + dropdown-menu primitives

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
proofkit-docs Ready Ready Preview May 4, 2026 7:37pm

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 4, 2026

⚠️ No Changeset found

Latest commit: 043c471

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8eb2a2f3-1b6f-4a64-98c0-db21fd61eb4c

📥 Commits

Reviewing files that changed from the base of the PR and between 5c424bc and 043c471.

📒 Files selected for processing (17)
  • apps/docs/src/app/(home)/page.tsx
  • apps/docs/src/app/(home)/why-proofkit/page.tsx
  • apps/docs/src/app/docs/(docs)/layout.tsx
  • apps/docs/src/components/DownloadButton.tsx
  • apps/docs/src/components/ui/button-group.tsx
  • apps/docs/src/components/ui/dropdown-menu.tsx
  • apps/docs/src/components/ui/svgs/claudeAiIcon.tsx
  • apps/docs/src/components/ui/svgs/claudeAiWordmarkIconDark.tsx
  • apps/docs/src/components/ui/svgs/claudeAiWordmarkIconLight.tsx
  • apps/docs/src/components/ui/svgs/codexDark.tsx
  • apps/docs/src/components/ui/svgs/codexLight.tsx
  • apps/docs/src/components/ui/svgs/codexWordmarkDark.tsx
  • apps/docs/src/components/ui/svgs/codexWordmarkLight.tsx
  • apps/docs/src/components/ui/svgs/cursorDark.tsx
  • apps/docs/src/components/ui/svgs/cursorLight.tsx
  • apps/docs/src/components/ui/svgs/cursorWordmarkDark.tsx
  • apps/docs/src/components/ui/svgs/cursorWordmarkLight.tsx

📝 Walkthrough

Walkthrough

This PR standardizes “WebViewer” → “Web Viewer” across the repo, reorganizes and expands the ProofKit docs (adds an AI section and many Web Viewer concept/reference pages), replaces the marketing/home layout with new pages and components, and adds a large suite of client-side marketing/UI components and interactive example demo components plus a docs download API and related helper module.

Changes

Terminology Standardization

Layer / File(s) Summary
Text replacements
packages/*/CHANGELOG.md, packages/cli*/src/**/*, packages/cli*/template/fm-addon/ProofKitWV/*, packages/cli*/template/vite-wv/**/*, packages/webviewer/src/**/*, _artifacts/*, apps/docs/src/app/llms*
Replaced occurrences of WebViewer with Web Viewer in changelogs, JSDoc/comments, user-facing messages, localization XML strings, test fixtures, SKILL/docs artifacts, and small UI text.
Docs terminology
apps/docs/content/docs/**/*.mdx, packages/*/skills/**/*.md
Updated documentation and generated artifact text to use the spaced “Web Viewer” phrasing.

Documentation Restructure & New Guides

Layer / File(s) Summary
Top-level nav & metadata
apps/docs/content/docs/meta.json, apps/docs/content/docs/ai/meta.json, apps/docs/content/docs/webviewer/meta.json, apps/docs/content/docs/cli/meta.json
Reorganized docs navigation: promote ai section, remove CLI webviewer pages from CLI nav, and restructure Web Viewer docs into Concept/Reference groupings.
New AI guides
apps/docs/content/docs/ai/*.mdx (index, getting-started, install-and-connect, explore-your-file, build-a-webviewer-app, deploy-to-filemaker, agent-workflow, technical-requirements, faq)
Added nine new AI-focused documentation pages covering install/connect, exploration, build loop, deployment, workflow, and technical requirements.
Web Viewer concept/reference pages
apps/docs/content/docs/webviewer/*.mdx (architecture, data-access, runtime-under-the-hood, filemaker-scripts-as-backend, fm-bridge, platform-notes, package, troubleshooting, why-webviewers)
Rewrote and expanded Web Viewer docs into conceptual and reference pages; removed older CLI-focused WebViewer MDX pages.
Cross-link & path updates
apps/docs/content/docs/cli/index.mdx, apps/docs/content/docs/fmdapi/*.mdx, apps/docs/content/docs/typegen/*.mdx
Updated internal links to new doc paths (e.g., /docs/webviewer/package, /docs/ai/getting-started) and adjusted related explanations.
Next.js redirects & download routes
apps/docs/next.config.ts, apps/docs/src/app/download/*
Added redirects from legacy docs routes to new locations and new download endpoints with a helper _lib.ts that validates platform/version and redirects to manifest assets.

Marketing, Home Redesign & Layout

Layer / File(s) Summary
Home layout change
apps/docs/src/app/(home)/layout.tsx
Removed HomeLayout wrapper; pages render children directly to enable custom marketing layouts.
Home & marketing pages
apps/docs/src/app/(home)/page.tsx, examples/page.tsx, how-it-works/page.tsx, why-proofkit/page.tsx, why-webviewers/page.tsx
Replaced landing content with a marketing-driven hero and several new marketing pages (examples, how-it-works, why-proofkit, why-webviewers).
Marketing nav & primitives
apps/docs/src/components/MarketingNav.tsx, marketing-section.tsx
Added responsive marketing navigation and marketing section/card primitives used across new pages.
Logo & nav image
apps/docs/src/app/layout.config.tsx, apps/docs/src/components/ProofkitLogo.tsx
Switched nav title to responsive light/dark images and changed ProofkitLogo to image-based span with forwarded props.
Next.js docs layout banner
apps/docs/src/app/docs/(docs)/layout.tsx
Inserted an Early Preview banner above DocsLayout.

UI Components, Interactive Examples & Visuals

Layer / File(s) Summary
Complex visuals
apps/docs/src/components/DarkVeil.tsx, apps/docs/src/components/DataFlowDiagram.tsx, .../DataFlowDiagramLazy.tsx
Added a WebGL-based DarkVeil and a large, timeline-driven DataFlowDiagram with responsive layout and animated elements (comets, cursor, pulses).
Examples infrastructure
apps/docs/src/components/examples/ExampleShowcaseGrid.tsx, ExamplesGallery.tsx, DesktopWindowFrame.tsx
Added grid/gallery, lightbox, and framed preview primitives to showcase live example components.
Interactive example components
apps/docs/src/components/examples/* (ApprovalInbox, CommandCenter, CustomerWorkspace, DashboardWithCharts, DataGridWithFiltering, DocumentCenter, InteractiveCalendar, InventoryTracker, KanbanBoard, ReportingBuilder, RichRecordForm, ServiceDispatch)
Added ~12 interactive/demo components implementing common business UI patterns; each supports non-interactive preview and an interactive mode via an interactive prop.
UI primitives & icons
apps/docs/src/components/ui/{button-group,dropdown-menu}.tsx, apps/docs/src/components/ui/svgs/*, ProofLogo.tsx, ShadcnPresetThemes.tsx, Mermaid.tsx, DownloadButton.tsx
Added button-group primitives, Radix-wrapped dropdown menu components, ~10 SVG icon components (Claude/Codex/Cursor variants and wordmarks), a Proof logo component, shadcn preset UI, a Mermaid renderer for MDX, and a platform-aware download button.
MDX integration
apps/docs/src/mdx-components.tsx, apps/docs/source.config.ts
Registered the Mermaid component in MDX and added remarkMdxMermaid to MDX plugin config.
Docs components registry
apps/docs/components.json
Extended component registries with @react-bits and @svgl.

Config, scripts & minor repo hygiene

Layer / File(s) Summary
New deps & package changes
apps/docs/package.json
Added dependencies: @dnd-kit/core, beautiful-mermaid, ogl, and radix-ui.
Download helper & routes
apps/docs/src/app/download/_lib.ts, apps/docs/src/app/download/[platform]/route.ts, .../[version]/route.ts
Implemented manifest fetching, version selection, platform asset picking, and redirection logic with Zod validation and explicit HTTP error handling.
Build & utility scripts
scripts/build-addon.js, scripts/check-skill-versions.mjs, scripts/sync-skill-versions.mjs
Minor refactors and formatting edits (imports, numeric literals, string handling) without behavioral changes.
Types/config/hygiene
env.d.ts, tsconfig.json, btca.config.jsonc, .gitignore, README.md, .claude/CLAUDE.md
Formatting/typing regeneration for env types, small tsconfig whitespace fix, updated gitignore entry for internal plans, README package ordering tweak, and monorepo structure formatting within CLAUDE.md.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/docs-landing-hero-header

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
_artifacts/skill_spec.md (1)

68-77: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the subsection count to match the added failure mode.

You added item #6 at Line 77, but the header still says webviewer-integration (5 failure modes). Please update the heading to avoid inconsistency.

✏️ Suggested edit
-### webviewer-integration (5 failure modes)
+### webviewer-integration (6 failure modes)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@_artifacts/skill_spec.md` around lines 68 - 77, The header
"webviewer-integration (5 failure modes)" is out of sync with the added failure
mode `#6`; update the subsection title to "webviewer-integration (6 failure
modes)" so it matches the list and prevents confusion — locate the header string
"webviewer-integration (5 failure modes)" in the document and change the numeral
to 6, ensuring the table of failure modes (entries 1–6) remains unchanged.
🟠 Major comments (22)
scripts/sync-skill-versions.mjs-38-53 (1)

38-53: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t suppress sync failures with an empty catch.

Line 52 hides I/O and JSON errors, so the command can finish “successfully” while skipping packages that failed to sync.

Suggested fix
-  } catch {}
+  } catch (error) {
+    if (error?.code === "ENOENT" && error?.path === skillsDir) {
+      continue;
+    }
+    console.error(`failed to sync ${relative(root, packageDir)}:`, error);
+    process.exit(1);
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/sync-skill-versions.mjs` around lines 38 - 53, The empty catch
swallowing errors around JSON.parse/readFileSync/writeFileSync must be replaced
so sync failures are surfaced: change the catch {} on the try that reads
packageJsonPath, calls getSkillFiles(skillsDir), and writes skill files
(affecting packageJson, updatedCount, and console.log output) to capture the
error (e.g. catch (err)) and either log a clear error including the file or
packageJsonPath and err (using console.error or processLogger) and exit non‑zero
or rethrow the error so the command fails visibly when I/O or JSON errors occur.
scripts/check-skill-versions.mjs-38-56 (1)

38-56: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail on unexpected read/parse errors instead of silently passing checks.

Line 55 swallows all exceptions, which can make the script report success even when validation never ran for some packages (e.g., malformed package.json, permission/read failures).

Suggested fix
-  } catch {}
+  } catch (error) {
+    // Allow packages without a skills directory, but fail on everything else.
+    if (error?.code === "ENOENT" && error?.path === skillsDir) {
+      continue;
+    }
+    console.error(`failed to check ${relative(root, packageDir)}:`, error);
+    process.exit(1);
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check-skill-versions.mjs` around lines 38 - 56, The try/catch around
reading/parsing package.json and skill files currently swallows all exceptions
(the empty catch block), so failures (e.g., JSON.parse on packageJsonPath or
readFileSync on skill files) are ignored; update the catch to surface errors by
logging the error (include context like packageJsonPath, skillsDir, or the
failing skillFile) and terminate with non-zero status (e.g., throw the error or
call process.exit(1)) so that mismatches detection fails loudly; make the change
around the block that uses JSON.parse(packageJsonPath),
getSkillFiles(skillsDir), readFileSync and the mismatches array so any
read/parse errors are not silently ignored.
apps/docs/src/components/examples/DocumentCenter.tsx-88-92 (1)

88-92: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle the “all archived” state to avoid stale/invalid selection.

Lines 88–92 can fall back to documents[0] even when it is archived, and Lines 111–121 then allow repeated archive actions against an already archived selection. This creates inconsistent state/UI when all docs are archived.

Suggested fix (null-safe selection + dedupe archive)
-  const selectedDocument =
-    documents.find((document) => document.id === selectedDocumentId && !archivedIds.includes(document.id)) ??
-    filteredDocuments[0] ??
-    documents[0];
+  const selectedDocument =
+    filteredDocuments.find((document) => document.id === selectedDocumentId) ?? filteredDocuments[0] ?? null;

   const archiveSelected = () => {
-    if (!interactive) {
+    if (!interactive || !selectedDocument) {
       return;
     }

-    setArchivedIds((current) => [...current, selectedDocument.id]);
+    setArchivedIds((current) =>
+      current.includes(selectedDocument.id) ? current : [...current, selectedDocument.id],
+    );
     const nextDocument = filteredDocuments.find((document) => document.id !== selectedDocument.id);

     if (nextDocument) {
       setSelectedDocumentId(nextDocument.id);
     }
   };

Also render an explicit empty-state in the aside when selectedDocument === null.

Also applies to: 111-121

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/DocumentCenter.tsx` around lines 88 - 92,
The selection logic currently falls back to documents[0] even if it’s archived;
change the selectedDocument computation (the variable assigned from
documents.find, filteredDocuments[0], documents[0]) to return null when no
unarchived document exists (i.e., remove the final fallback to documents[0] and
make it null-safe), update the aside rendering to show an explicit empty-state
when selectedDocument === null, and in the archive flow (the handler used around
lines 111–121) guard against duplicate archiving by checking selectedDocument &&
!archivedIds.includes(selectedDocument.id) before performing the archive, then
clear/set selectedDocument to null after archiving to avoid stale selection/UI.
apps/docs/src/components/examples/DocumentCenter.tsx-253-275 (1)

253-275: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Icon-only action buttons need accessible names.

Lines 253–275 render icon-only buttons (download/share/archive) without aria-label, so screen readers cannot identify their purpose.

Suggested fix
             <button
+              aria-label="Download selected document"
               className={cn(buttonVariants({ variant: "outline" }), "rounded-full")}
               disabled={!interactive}
               type="button"
             >
               <Download className="size-4" />
             </button>
             <button
+              aria-label="Share selected document"
               className={cn(buttonVariants({ variant: "outline" }), "rounded-full")}
               disabled={!interactive}
               type="button"
             >
               <Share2 className="size-4" />
             </button>
             <button
+              aria-label="Archive selected document"
               className={cn(buttonVariants({ variant: "outline" }), "rounded-full")}
               disabled={!interactive}
               onClick={archiveSelected}
               type="button"
             >
               <Archive className="size-4" />
             </button>

As per coding guidelines, "Use semantic HTML and ARIA attributes for accessibility".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/DocumentCenter.tsx` around lines 253 - 275,
The icon-only buttons (using Download, Share2, Archive components and
buttonVariants) lack accessible names; update each button to provide an
accessible label (e.g., add aria-label="Download", aria-label="Share",
aria-label="Archive" or use aria-labelledby pointing to a visually-hidden text)
and keep the existing disabled/interactive logic and the archiveSelected handler
intact so screen readers can announce the button purpose.
apps/docs/src/components/examples/DocumentCenter.tsx-149-155 (1)

149-155: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add an accessible label for the search input.

Line 149–155 uses placeholder-only text, which is not a sufficient accessible name for form controls. Add a visible or sr-only label linked via htmlFor/id.

Suggested fix
+              <label htmlFor="document-center-search" className="sr-only">
+                Search documents
+              </label>
               <input
+                id="document-center-search"
                 className="h-10 w-full rounded-full border border-border bg-background pr-4 pl-11 text-sm outline-none transition placeholder:text-muted-foreground focus:border-[`#D15ABB`]/60 focus:ring-2 focus:ring-[`#D15ABB`]/15"
                 disabled={!interactive}
                 onChange={(event) => setQuery(event.target.value)}
                 placeholder="Search files, accounts, or status..."
                 value={query}
               />

As per coding guidelines, "Use semantic HTML and ARIA attributes for accessibility: ... Add labels for form inputs".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/DocumentCenter.tsx` around lines 149 - 155,
The search input in the DocumentCenter component lacks an accessible name
because it relies only on placeholder text; add a proper label tied to the input
by giving the input an id (e.g., search-input) and adding either a visible
<label htmlFor="search-input">Search</label> or a visually-hidden/sr-only label
element so screen readers can announce the control — update the input element
that uses value={query} and onChange={(event) => setQuery(event.target.value)}
to include the new id and ensure the label text clearly describes the purpose
(e.g., "Search files, accounts, or status").
apps/docs/src/components/examples/DashboardWithCharts.tsx-50-61 (1)

50-61: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use semantic interactive elements for nav items and Export control.

<div> is used for controls that look interactive, which hurts keyboard/screen-reader accessibility. Use <button> (or links where applicable) and add basic semantics.

Suggested fix
-        <nav className="mt-8 space-y-1.5 text-sm">
+        <nav className="mt-8 space-y-1.5 text-sm" aria-label="Primary">
           {["Dashboard", "Pipeline", "Accounts", "Reports"].map((item, index) => (
-            <div
+            <button
+              type="button"
               className={cn(
                 "rounded-xl px-3 py-2.5 font-medium text-muted-foreground",
                 index === 0 && "bg-background text-foreground shadow-sm",
               )}
               key={item}
+              aria-current={index === 0 ? "page" : undefined}
             >
               {item}
-            </div>
+            </button>
           ))}
         </nav>
@@
-          <div className="inline-flex items-center gap-2 rounded-full border border-border bg-background px-4 py-2 font-medium text-sm shadow-sm">
+          <button
+            type="button"
+            className="inline-flex items-center gap-2 rounded-full border border-border bg-background px-4 py-2 font-medium text-sm shadow-sm"
+          >
             Export
             <ArrowUpRight className="size-4" />
-          </div>
+          </button>
As per coding guidelines, "Use semantic HTML and ARIA attributes for accessibility ... Use semantic elements (``, ``, etc.) instead of divs with roles."

Also applies to: 86-89

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/DashboardWithCharts.tsx` around lines 50 -
61, The nav items are rendered as non-semantic <div>s which breaks
keyboard/assistive accessibility; replace the mapped <div> used in the nav (the
array mapping producing "Dashboard", "Pipeline", etc.) with semantic interactive
elements (use <button type="button"> for controls or <a> for navigation), keep
the existing cn(...) classes, preserve the key={item}, and add an accessible
state like aria-current="page" (for the active item where index === 0) and
focus-visible styles; do the same replacement for the Export control referenced
later (the control at the other occurrence) so both controls use semantic
elements and basic ARIA/focus attributes.
apps/docs/src/components/examples/InventoryTracker.tsx-151-157 (1)

151-157: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add an explicit label for the search input.

The input is currently placeholder-only; screen readers need a programmatic label.

Suggested fix
+              <label className="sr-only" htmlFor="inventory-search">
+                Search inventory
+              </label>
               <input
+                id="inventory-search"
                 className="h-10 w-full rounded-full border border-border bg-background pr-4 pl-11 text-sm outline-none transition placeholder:text-muted-foreground focus:border-[`#D15ABB`]/60 focus:ring-2 focus:ring-[`#D15ABB`]/15"
                 disabled={!interactive}
                 onChange={(event) => setQuery(event.target.value)}
                 placeholder="Search item, SKU, bin..."
                 value={query}
               />

As per coding guidelines, Use semantic HTML and ARIA attributes for accessibility: ... Add labels for form inputs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/InventoryTracker.tsx` around lines 151 -
157, The search input in InventoryTracker is placeholder-only and needs a
programmatic label for screen readers; add a label element (e.g., <label
className="sr-only">Search inventory</label>) associated with the input by
adding an id (e.g., id="inventory-search") to the input and setting htmlFor on
the label, or alternatively add an accessible aria-label on the input; update
the input that uses value={query} and onChange={(event) =>
setQuery(event.target.value)} to include the id and/or aria-label so screen
readers can identify it.
apps/docs/src/components/examples/ReportingBuilder.tsx-301-307 (1)

301-307: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Line chart is not representing row data.

In Line 306, height is derived from index, so the chart can visually contradict the underlying revenue values. This makes the visualization misleading.

Proposed fix
 if (chartType === "line") {
+  const maxRevenue = Math.max(...rows.map((row) => row.revenue), 1);
   return (
     <div className="mt-6 flex h-56 items-end gap-5 border-border border-b px-4">
-      {rows.map((row, index) => (
+      {rows.map((row) => (
         <div className="flex flex-1 flex-col items-center gap-2" key={row.label}>
-          <div className="w-full rounded-t-xl bg-[`#D15ABB`]" style={{ height: `${70 + index * 24}px` }} />
-          <span className="text-muted-foreground text-xs">{index + 1}</span>
+          <div
+            className="w-full rounded-t-xl bg-[`#D15ABB`]"
+            style={{ height: `${Math.max((row.revenue / maxRevenue) * 160, 16)}px` }}
+          />
+          <span className="text-muted-foreground text-xs">${Math.round(row.revenue / 1000)}K</span>
         </div>
       ))}
     </div>
   );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/ReportingBuilder.tsx` around lines 301 -
307, The line chart's bar heights are currently computed from the array index
(inside the chartType === "line" block where rows.map renders each bar and sets
style={{ height: `${70 + index * 24}px` }}), which misrepresents actual data;
change this to compute each bar's height from the row's numeric value (e.g.,
row.value or row.revenue) by first deriving a maxValue = Math.max(...rows.map(r
=> r.value ?? r.revenue ?? 0)) and then computing a normalized height (e.g.,
baseHeight + (rowValue / maxValue) * maxExtraHeight) to use in the style height,
so rows.map and the style use real data rather than index.
apps/docs/src/components/ui/svgs/claudeAiIcon.tsx-3-4 (1)

3-4: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add an SVG title to satisfy a11y and unblock CI.

Line 4 fails biome lint/a11y/noSvgWithoutTitle. Add a non-empty <title> (or explicit decorative handling) on the root <svg>.

Suggested fix
-import type { SVGProps } from "react";
+import type { SVGProps } from "react";

-const ClaudeAiIcon = (props: SVGProps<SVGSVGElement>) => (
-  <svg {...props} preserveAspectRatio="xMidYMid" viewBox="0 0 256 257">
+type ClaudeAiIconProps = SVGProps<SVGSVGElement> & { title?: string };
+
+const ClaudeAiIcon = ({ title = "Claude AI icon", ...props }: ClaudeAiIconProps) => (
+  <svg {...props} preserveAspectRatio="xMidYMid" viewBox="0 0 256 257" role="img">
+    <title>{title}</title>

As per coding guidelines, "Use semantic HTML and ARIA attributes for accessibility: Provide meaningful alt text for images".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/ui/svgs/claudeAiIcon.tsx` around lines 3 - 4, The
ClaudeAiIcon component's root <svg> is missing an accessible <title>; add a
non-empty <title> element inside the root SVG (or mark the SVG explicitly
decorative with aria-hidden="true" and role="img" handling) so it passes a11y
lint. Specifically, update the ClaudeAiIcon component to render either a
<title>{props.title ?? "Claude AI"}</title> (and forward an optional title prop)
or set aria-hidden="true" when decorative, ensuring the root <svg> includes the
title or the explicit decorative attributes to satisfy biome's
a11y/noSvgWithoutTitle rule.
apps/docs/src/components/ui/svgs/claudeAiWordmarkIconLight.tsx-3-4 (1)

3-4: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Provide a non-empty <title> for the wordmark SVG.

Line 4 violates biome lint/a11y/noSvgWithoutTitle; this currently fails the pipeline.

Suggested fix
-import type { SVGProps } from "react";
+import type { SVGProps } from "react";

-const ClaudeAiWordmarkIconLight = (props: SVGProps<SVGSVGElement>) => (
-  <svg {...props} preserveAspectRatio="xMidYMid" viewBox="0 0 512 110">
+type ClaudeAiWordmarkIconLightProps = SVGProps<SVGSVGElement> & { title?: string };
+
+const ClaudeAiWordmarkIconLight = ({
+  title = "Claude AI wordmark",
+  ...props
+}: ClaudeAiWordmarkIconLightProps) => (
+  <svg {...props} preserveAspectRatio="xMidYMid" viewBox="0 0 512 110" role="img">
+    <title>{title}</title>

As per coding guidelines, "Use semantic HTML and ARIA attributes for accessibility: Provide meaningful alt text for images".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/ui/svgs/claudeAiWordmarkIconLight.tsx` around lines
3 - 4, The SVG rendered by the ClaudeAiWordmarkIconLight component is missing a
non-empty <title>, causing an accessibility lint failure; add a meaningful title
element (e.g., "Claude AI wordmark") inside the SVG and reference it from the
SVG via aria-labelledby (or set role="img" and provide the title) so screen
readers can announce it—update the ClaudeAiWordmarkIconLight component to
include a title element with a unique id and add aria-labelledby="that-id" (or
role="img") on the <svg>.
apps/docs/src/components/ui/svgs/claudeAiWordmarkIconDark.tsx-3-4 (1)

3-4: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add SVG title metadata for accessibility compliance.

Line 4 triggers biome lint/a11y/noSvgWithoutTitle; add a <title> element on the SVG.

Suggested fix
-import type { SVGProps } from "react";
+import type { SVGProps } from "react";

-const ClaudeAiWordmarkIconDark = (props: SVGProps<SVGSVGElement>) => (
-  <svg {...props} preserveAspectRatio="xMidYMid" viewBox="0 0 512 110">
+type ClaudeAiWordmarkIconDarkProps = SVGProps<SVGSVGElement> & { title?: string };
+
+const ClaudeAiWordmarkIconDark = ({
+  title = "Claude AI wordmark",
+  ...props
+}: ClaudeAiWordmarkIconDarkProps) => (
+  <svg {...props} preserveAspectRatio="xMidYMid" viewBox="0 0 512 110" role="img">
+    <title>{title}</title>

As per coding guidelines, "Use semantic HTML and ARIA attributes for accessibility: Provide meaningful alt text for images".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/ui/svgs/claudeAiWordmarkIconDark.tsx` around lines 3
- 4, The SVG returned by ClaudeAiWordmarkIconDark lacks a <title> element and
ARIA labeling, triggering the a11y/noSvgWithoutTitle lint; update the
ClaudeAiWordmarkIconDark component to include a descriptive <title> element
(e.g., "Claude AI wordmark") inside the <svg> and wire it to the SVG via
aria-labelledby (or aria-label) so screen readers can announce it; ensure the
title has a stable id (or accept a title/titleId prop) and that the SVG includes
aria-labelledby="{id}" (or aria-label) while preserving existing props spread.
apps/docs/src/components/ui/svgs/cursorDark.tsx-3-5 (1)

3-5: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add accessible SVG labeling to fix CI and a11y compliance.

The root <svg> has no non-empty <title>, which triggers biome lint/a11y/noSvgWithoutTitle and blocks the pipeline. Add a title (or explicitly mark decorative usage).

🔧 Suggested fix
 import type { SVGProps } from "react";
 
-const CursorDark = (props: SVGProps<SVGSVGElement>) => (
-  <svg {...props} id="Ebene_1" version="1.1" viewBox="0 0 466.73 532.09">
+type CursorDarkProps = SVGProps<SVGSVGElement> & { title?: string };
+
+const CursorDark = ({ title = "Cursor dark logo", ...props }: CursorDarkProps) => (
+  <svg {...props} role="img" aria-label={title} viewBox="0 0 466.73 532.09">
+    <title>{title}</title>
     <path d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z" />
   </svg>
 );

As per coding guidelines, Use semantic HTML and ARIA attributes for accessibility: Provide meaningful alt text for images.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/ui/svgs/cursorDark.tsx` around lines 3 - 5, The
CursorDark component's root <svg> is missing an accessible non-empty title
(triggers biome lint a11y/noSvgWithoutTitle); update the CursorDark SVG to
include a <title> with meaningful text (or if purely decorative, add
aria-hidden="true" and focusable="false" and role="img" as appropriate) and
ensure the title ID is referenced via aria-labelledby on the <svg> when not
decorative so screen readers pick it up; locate the CursorDark
function/component and modify its returned <svg> element accordingly.
apps/docs/src/components/ui/svgs/cursorLight.tsx-3-5 (1)

3-5: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing SVG <title> causes accessibility lint failure.

This component hits the same a11y/noSvgWithoutTitle error on Line 4. Please add a non-empty <title> (or mark the SVG decorative).

🔧 Suggested fix
 import type { SVGProps } from "react";
 
-const CursorLight = (props: SVGProps<SVGSVGElement>) => (
-  <svg {...props} id="Ebene_1" version="1.1" viewBox="0 0 466.73 532.09">
+type CursorLightProps = SVGProps<SVGSVGElement> & { title?: string };
+
+const CursorLight = ({ title = "Cursor light logo", ...props }: CursorLightProps) => (
+  <svg {...props} role="img" aria-label={title} viewBox="0 0 466.73 532.09">
+    <title>{title}</title>
     <path d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z" />
   </svg>
 );

As per coding guidelines, Use semantic HTML and ARIA attributes for accessibility: Provide meaningful alt text for images.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/ui/svgs/cursorLight.tsx` around lines 3 - 5, The SVG
component CursorLight is missing an accessible title which triggers
a11y/noSvgWithoutTitle; update the SVG element in the CursorLight component to
include a non-empty <title> element describing the icon (e.g., a concise
descriptive string) and ensure the title is uniquely associated (kept as first
child of the <svg>) or, if the SVG is purely decorative, mark it as decorative
by adding aria-hidden="true" and role="img" as appropriate; modify CursorLight's
JSX accordingly so linting and screen readers receive the correct metadata.
apps/docs/src/components/ui/svgs/cursorWordmarkLight.tsx-3-5 (1)

3-5: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wordmark SVG needs a non-empty title for accessibility compliance.

The <svg> on Line 4 is missing a <title>, so Biome flags a11y/noSvgWithoutTitle and the workflow fails.

🔧 Suggested fix
 import type { SVGProps } from "react";
 
-const CursorWordmarkLight = (props: SVGProps<SVGSVGElement>) => (
-  <svg {...props} id="Ebene_1" version="1.1" viewBox="0 0 1655.29 278.83">
+type CursorWordmarkLightProps = SVGProps<SVGSVGElement> & { title?: string };
+
+const CursorWordmarkLight = ({ title = "Cursor wordmark light logo", ...props }: CursorWordmarkLightProps) => (
+  <svg {...props} role="img" aria-label={title} viewBox="0 0 1655.29 278.83">
+    <title>{title}</title>
     <path d="M139.03,4.61h90.64v49.93h-87.57c-47.24,0-84.11,27.27-84.11,84.88s36.87,84.88,84.11,84.88h87.57v49.93h-94.48c-79.12,0-135.19-46.48-135.19-134.82S59.91,4.61,139.03,4.61Z" />
     <path d="M275.75,4.61h56.07v164.76c0,41.09,18.82,60.3,62.99,60.3s62.99-19.2,62.99-60.3V4.61h56.07v176.28c0,59.91-38.02,97.94-119.06,97.94s-119.06-38.41-119.06-98.32V4.61Z" />
     <path d="M806.9,81.04c0,29.96-17.28,53-40.33,62.99v.77c24.2,3.46,36.49,20.74,36.87,44.17l1.15,85.26h-56.07l-1.15-76.04c-.38-16.9-10.37-27.27-30.34-27.27h-93.33v103.31h-56.07V4.61h154.78c50.7,0,84.49,25.73,84.49,76.43h0ZM750.45,88.72c0-23.04-12.29-35.72-35.33-35.72h-91.41v71.43h92.17c21.12,0,34.57-12.67,34.57-35.72h0Z" />

As per coding guidelines, Use semantic HTML and ARIA attributes for accessibility: Provide meaningful alt text for images.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/ui/svgs/cursorWordmarkLight.tsx` around lines 3 - 5,
The SVG in the CursorWordmarkLight component lacks a non-empty <title>, causing
an accessibility failure; add a descriptive <title> element (e.g., "Cursor
wordmark" or other meaningful text) inside the <svg> and wire it for
accessibility by giving the title a unique id and referencing it from the svg
via aria-labelledby (and ensure role="img" is present or preserved) so screen
readers can announce the wordmark; update CursorWordmarkLight to include these
attributes and the title.
apps/docs/src/components/ui/svgs/cursorWordmarkDark.tsx-3-5 (1)

3-5: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Dark wordmark SVG is missing required accessible title metadata.

a11y/noSvgWithoutTitle is triggered here as well. Add a non-empty <title> and label the SVG, or mark it decorative when appropriate.

🔧 Suggested fix
 import type { SVGProps } from "react";
 
-const CursorWordmarkDark = (props: SVGProps<SVGSVGElement>) => (
-  <svg {...props} id="Ebene_1" version="1.1" viewBox="0 0 1655.29 278.83">
+type CursorWordmarkDarkProps = SVGProps<SVGSVGElement> & { title?: string };
+
+const CursorWordmarkDark = ({ title = "Cursor wordmark dark logo", ...props }: CursorWordmarkDarkProps) => (
+  <svg {...props} role="img" aria-label={title} viewBox="0 0 1655.29 278.83">
+    <title>{title}</title>
     <path d="M139.03,4.61h90.64v49.93h-87.57c-47.24,0-84.11,27.27-84.11,84.88s36.87,84.88,84.11,84.88h87.57v49.93h-94.48c-79.12,0-135.19-46.48-135.19-134.82S59.91,4.61,139.03,4.61Z" />
     <path d="M275.75,4.61h56.07v164.76c0,41.09,18.82,60.3,62.99,60.3s62.99-19.2,62.99-60.3V4.61h56.07v176.28c0,59.91-38.02,97.94-119.06,97.94s-119.06-38.41-119.06-98.32V4.61Z" />
     <path d="M806.9,81.04c0,29.96-17.28,53-40.33,62.99v.77c24.2,3.46,36.49,20.74,36.87,44.17l1.15,85.26h-56.07l-1.15-76.04c-.38-16.9-10.37-27.27-30.34-27.27h-93.33v103.31h-56.07V4.61h154.78c50.7,0,84.49,25.73,84.49,76.43h0ZM750.45,88.72c0-23.04-12.29-35.72-35.33-35.72h-91.41v71.43h92.17c21.12,0,34.57-12.67,34.57-35.72h0Z" />

As per coding guidelines, Use semantic HTML and ARIA attributes for accessibility: Provide meaningful alt text for images.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/ui/svgs/cursorWordmarkDark.tsx` around lines 3 - 5,
The SVG in the CursorWordmarkDark component lacks accessible title metadata;
update the <svg> returned by CursorWordmarkDark to include a non-empty <title>
element and associate it via aria-labelledby (e.g., add <title
id="cursor-wordmark-title">Cursor wordmark</title> and set
aria-labelledby="cursor-wordmark-title" and role="img" on the <svg>). If the
graphic is purely decorative, instead mark the <svg> as aria-hidden="true" and
focusable="false" rather than adding a title—apply the change inside the
CursorWordmarkDark component's svg element accordingly.
apps/docs/src/app/layout.config.tsx-14-15 (1)

14-15: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Horizontal logo dimensions look reversed.

Line 14 and Line 15 use height={150} with width={96} for logo-horiz-* assets, which likely stretches/compresses the header logo. Use the true asset ratio (or swap width/height if these were inverted).

💡 Suggested adjustment
-        <Image alt="ProofKit" className="block dark:hidden" height={150} src="/logo-horiz-light.svg" width={96} />
-        <Image alt="ProofKit" className="hidden dark:block" height={150} src="/logo-horiz-dark.svg" width={96} />
+        <Image alt="ProofKit" className="block dark:hidden" height={96} src="/logo-horiz-light.svg" width={150} />
+        <Image alt="ProofKit" className="hidden dark:block" height={96} src="/logo-horiz-dark.svg" width={150} />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/app/layout.config.tsx` around lines 14 - 15, The horizontal
logo Image elements (the two Image components rendering "logo-horiz-light.svg"
and "logo-horiz-dark.svg") have inverted dimensions (height={150} width={96})
causing distortion; update those Image props to use the correct aspect
ratio—swap to width={150} height={96} or set the actual asset dimensions (e.g.,
width matching the horizontal logo's larger dimension) so both <Image ...
src="logo-horiz-light.svg"> and <Image ... src="logo-horiz-dark.svg"> use the
proper width/height values consistent with the asset.
apps/docs/src/components/ProofLogo.tsx-10-12 (1)

10-12: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid static SVG gradient IDs in reusable component instances.

Multiple ProofLogo renders can collide on shared DOM IDs and produce incorrect gradient fills. Use per-instance IDs.

🔧 Suggested fix
+import { useId } from "react";
 import type React from "react";
@@
 export function ProofLogo({ className, ...props }: ProofLogoProps) {
+  const gradientIdPrefix = useId();
+  const lightGradientId = `${gradientIdPrefix}-proof-logo-light-gradient`;
+  const darkGradientId = `${gradientIdPrefix}-proof-logo-dark-gradient`;
+
   return (
@@
-            id="proof-logo-light-gradient"
+            id={lightGradientId}
@@
-              fill="url(`#proof-logo-light-gradient`)"
+              fill={`url(#${lightGradientId})`}
@@
-            id="proof-logo-dark-gradient"
+            id={darkGradientId}
@@
-              fill="url(`#proof-logo-dark-gradient`)"
+              fill={`url(#${darkGradientId})`}

Also applies to: 30-30, 40-41, 59-59

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/ProofLogo.tsx` around lines 10 - 12, The ProofLogo
component uses static SVG IDs (e.g., "proof-logo-light-gradient") which will
collide when multiple instances are mounted; generate a per-instance unique
suffix (use React's useId or a small uniqueId helper) and append it to every SVG
id and all corresponding references (e.g., linearGradient id attributes and
fill="url(`#proof-logo-light-gradient`)") so the gradient definitions and usages
remain paired per-instance; update every static gradient/id mentioned (including
the other gradient ids referenced around lines 30, 40 and 59) so both the id
attributes and any url(#...) references include the same unique suffix.
apps/docs/src/components/ShadcnPresetThemes.tsx-92-97 (1)

92-97: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add noopener on the external new-tab link.

Line 95 should include noopener alongside noreferrer when using target="_blank".

Suggested fix
-            rel="noreferrer"
+            rel="noopener noreferrer"

As per coding guidelines "Add rel=\"noopener\" when using target=\"_blank\" on links".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/ShadcnPresetThemes.tsx` around lines 92 - 97, The
anchor tag that opens an external site in a new tab (the <a
href="https://ui.shadcn.com/create" target="_blank"> element) is missing
rel="noopener"; update its rel attribute to include noopener alongside
noreferrer to prevent the opened page from accessing window.opener (i.e., change
rel="noreferrer" to rel="noreferrer noopener" on that anchor).
apps/docs/src/components/examples/CustomerWorkspace.tsx-205-210 (1)

205-210: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a programmatic label for the notes textarea.

Line 205 has an unlabeled form control; add a <label> or aria-label so screen readers can identify the field.

Suggested fix
                   <textarea
+                    aria-label="Customer notes"
                     className="min-h-40 w-full resize-none rounded-2xl border border-border bg-background p-4 text-sm outline-none transition focus:border-[`#D15ABB`]/60 focus:ring-2 focus:ring-[`#D15ABB`]/15"
                     disabled={!interactive}
                     onChange={(event) => setNote(event.target.value)}
                     value={note}
                   />

As per coding guidelines "Add labels for form inputs".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/CustomerWorkspace.tsx` around lines 205 -
210, The textarea used in CustomerWorkspace (the element with value={note},
onChange={(event) => setNote(event.target.value)} and disabled={!interactive})
lacks an accessible label; update the component to provide a programmatic label
by either wrapping the textarea with a <label> tied to a unique id (e.g.,
id="notes-textarea") or by adding aria-label="Notes" (or a more contextual
string) directly on the textarea, ensuring the id/aria-label reflects the
purpose and remains present when disabled so screen readers can identify the
field.
scripts/build-addon.js-207-207 (1)

207-207: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace shell-interpolated execSync calls with argument-safe process execution.

Lines 71, 207, and 222 use shell command interpolation, creating command-injection risks. Switch to execFileSync with argument arrays to eliminate shell parsing and interpolation vulnerabilities.

Suggested fixes:

Fix for line 207 (zip command)
-import { execSync } from "node:child_process";
+import { execFileSync, execSync } from "node:child_process";
...
-  execSync(`cd "${addonModulesDir}" && zip -r "${stageZipPath}" "${ADDON_NAME}"`);
+  execFileSync("zip", ["-r", stageZipPath, ADDON_NAME], { cwd: addonModulesDir });
Fix for line 71 (open command)
 function openPath(target) {
-  execSync(`open "${target}"`);
+  execFileSync("open", [target]);
 }
Fix for line 222 (cp command)
-  execSync(`cp -a "${addonFolderPath}/"* "${cliTemplatePath}/"`);
+  execFileSync("cp", ["-a", `${addonFolderPath}/*`, cliTemplatePath]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/build-addon.js` at line 207, Replace the shell-interpolated execSync
calls with argument-safe execFileSync calls: for the zip call that currently
uses execSync(`cd "${addonModulesDir}" && zip -r "${stageZipPath}"
"${ADDON_NAME}"`) remove shell interpolation and call execFileSync('zip', ['-r',
stageZipPath, ADDON_NAME], { cwd: addonModulesDir }) instead; similarly convert
the other occurrences (the open command at the earlier execSync and the cp
command later) to execFileSync with the program name and an args array and set
cwd or full paths via options; also ensure you import/require execFileSync from
child_process and preserve existing stdio/error handling.
apps/docs/src/components/examples/DataGridWithFiltering.tsx-163-168 (1)

163-168: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a programmatic label for the search input.

The input relies on placeholder text only; it needs an associated label for assistive technologies.

♿ Suggested fix
+<label className="sr-only" htmlFor="contacts-search">
+  Search contacts
+</label>
 <input
+  id="contacts-search"
   className="h-10 w-full rounded-full border border-border bg-background pr-10 pl-11 text-sm outline-none transition placeholder:text-muted-foreground focus:border-[`#D15ABB`]/60 focus:ring-2 focus:ring-[`#D15ABB`]/15"
   onChange={(event) => setQuery(event.target.value)}
   placeholder="Search name, company, email, owner, or stage..."
   value={query}
 />
As per coding guidelines, "Use semantic HTML and ARIA attributes for accessibility: ... Add labels for form inputs".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/DataGridWithFiltering.tsx` around lines 163
- 168, The search input in DataGridWithFiltering (the input using setQuery and
value={query}) lacks an accessible label; add a programmatic label by giving the
input an id and either rendering a visible or visually-hidden <label
htmlFor="...">Search</label> or add aria-label/aria-labelledby referencing that
label id so assistive tech can identify the field; ensure the id used by the
label matches the input id and keep the placeholder for visual hint only.
apps/docs/src/components/examples/RichRecordForm.tsx-252-257 (1)

252-257: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Associate visible field text with inputs using real <label> elements.

Field currently renders a <span>, so controls like Line 114, Line 125, and Line 200 are not programmatically labeled for assistive tech.

♿ Suggested fix
-function Field({ children, className, label }: { children: React.ReactNode; className?: string; label: string }) {
+function Field({
+  children,
+  className,
+  htmlFor,
+  label,
+}: {
+  children: React.ReactNode;
+  className?: string;
+  htmlFor?: string;
+  label: string;
+}) {
   return (
     <div className={cn("block", className)}>
-      <span className="mb-2 block font-medium text-muted-foreground text-xs uppercase tracking-[0.16em]">{label}</span>
+      <label className="mb-2 block font-medium text-muted-foreground text-xs uppercase tracking-[0.16em]" htmlFor={htmlFor}>
+        {label}
+      </label>
       {children}
     </div>
   );
}
-<Field label="Customer">
+<Field htmlFor="intake-customer" label="Customer">
   <input
+    id="intake-customer"
     ...
   />
 </Field>

-<Field label="Owner">
+<Field htmlFor="intake-owner" label="Owner">
   <input
+    id="intake-owner"
     ...
   />
 </Field>

-<Field className="mt-5" label="Internal notes">
+<Field className="mt-5" htmlFor="intake-notes" label="Internal notes">
   <textarea
+    id="intake-notes"
     ...
   />
 </Field>
As per coding guidelines, "Use semantic HTML and ARIA attributes for accessibility: ... Add labels for form inputs".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/RichRecordForm.tsx` around lines 252 - 257,
Field uses a non-semantic <span> for the visible field text which prevents
programmatic labeling; change Field to render a real <label> and accept an
htmlFor prop (e.g., function Field({ children, className, label, htmlFor })),
output <label htmlFor={htmlFor} className=...>{label}</label>, and then update
the associated input controls that currently use Field to set matching id
attributes (the inputs rendered where Field is used) so each input has id equal
to the htmlFor passed to Field; this ensures proper semantic association for
assistive tech.
🧹 Nitpick comments (10)
packages/cli/template/fm-addon/ProofKitWV/es.xml (1)

1019-1021: ⚡ Quick win

Consider updating "Webviewer" to "Web Viewer" for consistency.

The script name uses "Webviewer" (line 1020), which is inconsistent with the "Web Viewer" terminology being standardized in this PR. For complete consistency, consider updating this to "Refresh Web Viewer Widget".

Similar inconsistency exists at line 1068 with "UploadWebviewerWidget" (though that appears to be a script identifier where camelCase might be intentional).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli/template/fm-addon/ProofKitWV/es.xml` around lines 1019 - 1021,
Update the display strings and comment that read "Refresh Webviewer Widget" to
use the standardized spacing "Refresh Web Viewer Widget": specifically change
the <SourceText>, <TargetText>, and the <Comment> value "com.fmi.script.Refresh
Webviewer Widget" to use "Web Viewer". Do not change camelCase identifiers like
"UploadWebviewerWidget" unless you also intend to rename code identifiers; limit
this change to user-facing texts/comments in the es.xml entries.
apps/docs/src/components/examples/DashboardWithCharts.tsx (2)

122-122: ⚡ Quick win

Extract the chart scale multiplier into a named constant.

Line 122 uses 1.55 inline; this is a magic number and makes tuning harder.

Suggested fix
+const REVENUE_BAR_HEIGHT_SCALE = 1.55;
@@
-                    style={{ height: `${height * 1.55}px` }}
+                    style={{ height: `${height * REVENUE_BAR_HEIGHT_SCALE}px` }}
As per coding guidelines, "Use meaningful variable names instead of magic numbers - extract constants with descriptive names."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/DashboardWithCharts.tsx` at line 122, The
inline magic number 1.55 used in the style calculation (style={{ height:
`${height * 1.55}px` }}) should be extracted to a named constant for clarity and
easier tuning; add a descriptive constant (e.g., CHART_HEIGHT_MULTIPLIER) near
the top of the DashboardWithCharts component/file and replace the inline literal
with height * CHART_HEIGHT_MULTIPLIER so the multiplier is self-documenting and
adjustable.

157-167: ⚡ Quick win

Avoid positional tuple indexing for account rows; destructure named fields.

Using row[0], row[1], etc. is brittle and less readable. Prefer object rows with destructuring in the map callback.

Suggested fix
+const accountRows = [
+  { account: "Acme Services", owner: "Lena", value: "$18.4K", status: "Active" },
+  { account: "Northstar Labs", owner: "Miles", value: "$12.1K", status: "Review" },
+  { account: "Brightline Co.", owner: "Ari", value: "$9.7K", status: "Active" },
+] as const;
@@
-          {[
-            ["Acme Services", "Lena", "$18.4K", "Active"],
-            ["Northstar Labs", "Miles", "$12.1K", "Review"],
-            ["Brightline Co.", "Ari", "$9.7K", "Active"],
-          ].map((row) => (
-            <div className="grid grid-cols-[1.3fr_1fr_1fr_0.8fr] items-center py-3 text-sm" key={row[0]}>
-              <span className="font-medium">{row[0]}</span>
-              <span className="text-muted-foreground">{row[1]}</span>
-              <span>{row[2]}</span>
-              <Badge appearance={row[3] === "Active" ? "light" : "outline"} size="sm" variant="secondary">
-                {row[3]}
+          {accountRows.map(({ account, owner, value, status }) => (
+            <div className="grid grid-cols-[1.3fr_1fr_1fr_0.8fr] items-center py-3 text-sm" key={account}>
+              <span className="font-medium">{account}</span>
+              <span className="text-muted-foreground">{owner}</span>
+              <span>{value}</span>
+              <Badge appearance={status === "Active" ? "light" : "outline"} size="sm" variant="secondary">
+                {status}
               </Badge>
             </div>
           ))}
As per coding guidelines, "Use destructuring for object and array assignments."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/DashboardWithCharts.tsx` around lines 157 -
167, The map over positional tuples in DashboardWithCharts should be replaced
with an array of objects and a destructuring callback to improve readability and
safety: replace the current array-of-arrays with an array like [{company, owner,
revenue, status}, ...] and change the map callback to .map(({company, owner,
revenue, status}) => ( ... )) using those named variables (use company as the
key instead of row[0]) and preserve the Badge usage (appearance based on
status). Update any references to row[0]/row[1]/row[2]/row[3] accordingly to
company/owner/revenue/status.
apps/docs/src/components/examples/InventoryTracker.tsx (1)

141-145: ⚡ Quick win

Replace hardcoded incoming total with a derived constant.

"126" is a magic number and can drift from inventoryItems.

Suggested fix
 const categories = ["All", "Hardware", "Supplies", "Equipment"] as const;
+const TOTAL_INCOMING_UNITS = inventoryItems.reduce((sum, item) => sum + item.incoming, 0);
@@
-          <Metric label="Incoming" value="126" />
+          <Metric label="Incoming" value={String(TOTAL_INCOMING_UNITS)} />

As per coding guidelines, Use meaningful variable names instead of magic numbers - extract constants with descriptive names.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/InventoryTracker.tsx` around lines 141 -
145, The Incoming metric currently uses a hardcoded "126" magic number; instead
compute a descriptive constant (e.g., incomingTotal or incomingUnits) derived
from the inventory data (inventoryItems) and pass String(incomingTotal) to the
Metric for the "Incoming" label. Locate the Metric call for the Incoming card in
InventoryTracker (the third Metric in the div with className "mt-6 grid...") and
replace the literal with the computed constant, ensuring the calculation logic
(sum or filter of inventoryItems for incoming status) is implemented near where
totalUnits/lowStockCount are computed.
apps/docs/src/components/examples/ReportingBuilder.tsx (1)

55-56: ⚡ Quick win

Type dateRange from const-backed options for stronger safety.

dateRange is currently a free-form string. Constraining it to known literal options improves clarity and prevents accidental invalid states.

Proposed refactor
+const dateRanges = ["This month", "This quarter", "Year to date"] as const;
+type DateRange = (typeof dateRanges)[number];

-const [dateRange, setDateRange] = useState("This quarter");
+const [dateRange, setDateRange] = useState<DateRange>("This quarter");

- {["This month", "This quarter", "Year to date"].map((range) => (
+ {dateRanges.map((range) => (

As per coding guidelines, **/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity, and use const assertions (as const) for immutable values and literal types.

Also applies to: 212-227

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/ReportingBuilder.tsx` around lines 55 - 56,
dateRange is typed as a plain string; change it to a literal union by defining
the allowed options as a const tuple (e.g., const DATE_RANGE_OPTIONS = ["This
quarter", "This month", ...] as const), derive a type like DateRange = typeof
DATE_RANGE_OPTIONS[number], and update the state declaration to use
useState<DateRange>("This quarter") for dateRange and setDateRange; apply the
same const-backed literal typing pattern to other similar variables mentioned
(e.g., the block around the other occurrences) to ensure compile-time safety and
stronger intent.
apps/docs/src/components/ui/svgs/codexDark.tsx (1)

12-15: ⚡ Quick win

Consider extracting a shared base SVG component for dark/light variants.

codexDark.tsx and codexLight.tsx (and likewise the wordmarks) are nearly identical except color. A small shared base component would reduce duplication and path drift risk.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/ui/svgs/codexDark.tsx` around lines 12 - 15,
codexDark.tsx and codexLight.tsx (and the matching wordmark files) duplicate the
same SVG paths except for coloring; extract the shared SVG structure into a new
reusable component (e.g., CodexIconBase) that accepts props for
fill/color/className and any ARIA/size props, move the long <path d="...">
content into that base, then replace codexDark.tsx and codexLight.tsx with tiny
wrappers (e.g., CodexDark and CodexLight) that import CodexIconBase and pass the
appropriate color/theme props; do the same for the wordmark pair (e.g.,
WordmarkBase + WordmarkDark/WordmarkLight) and update imports where these
components are used to remove duplication and prevent path drift.
packages/cli-old/template/fm-addon/ProofKitWV/info_en.json (1)

2-3: ⚡ Quick win

Align Description terminology with the updated title

Line 2 now uses “Web Viewer”, but Line 3 still says “webviewer”. Updating Line 3 keeps user-facing naming consistent.

✏️ Suggested patch
-  "Description": "Easily build and embed your own custom webviewer widgets into FileMaker",
+  "Description": "Easily build and embed your own custom Web Viewer widgets into FileMaker",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli-old/template/fm-addon/ProofKitWV/info_en.json` around lines 2 -
3, Update the Description value so its terminology matches the Title: replace
the lowercase, concatenated "webviewer" with the same phrasing used in Title
("Web Viewer") in the JSON key "Description" (keep the rest of the sentence
intact and ensure capitalization and spacing match "Title": "ProofKit Web
Viewer").
apps/docs/src/components/DarkVeil.tsx (1)

98-100: 💤 Low value

Consider adding null guards for defensive coding.

The type assertions assume ref.current and canvas.parentElement are never null. While this should be safe since the canvas is rendered by this component, adding guards would make the code more robust.

♻️ Optional defensive check
  useEffect(() => {
-   const canvas = ref.current as HTMLCanvasElement;
-   const parent = canvas.parentElement as HTMLElement;
+   const canvas = ref.current;
+   const parent = canvas?.parentElement;
+   if (!canvas || !parent) return;

    const renderer = new Renderer({
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/DarkVeil.tsx` around lines 98 - 100, In DarkVeil's
useEffect, avoid unsafe type assertions by guarding ref.current and the canvas
parent before using them: check that ref.current exists (and is an
HTMLCanvasElement) and that canvas.parentElement exists, and return early if
either is null; update references in this effect (the variables canvas and
parent) and any downstream logic that assumes their existence to rely on those
guards to prevent runtime NPEs.
apps/docs/src/components/examples/ExampleShowcaseGrid.tsx (1)

165-170: ⚡ Quick win

Preserve and restore prior body overflow instead of hard-resetting it.

Using "" on cleanup can accidentally override an existing document overflow setting from other UI states.

🧩 Suggested fix
  useEffect(() => {
    if (!openExampleId) {
      return;
    }

+   const previousOverflow = document.body.style.overflow;
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") {
        setOpenExampleId(null);
      }
    };

    document.body.style.overflow = "hidden";
    window.addEventListener("keydown", handleKeyDown);

    return () => {
-     document.body.style.overflow = "";
+     document.body.style.overflow = previousOverflow;
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, [openExampleId]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/ExampleShowcaseGrid.tsx` around lines 165 -
170, When setting document.body.style.overflow to "hidden" (in the same effect
that adds window.addEventListener("keydown", handleKeyDown)), capture the prior
value into a local variable (e.g., previousOverflow =
document.body.style.overflow) before mutating it and then on cleanup restore
document.body.style.overflow = previousOverflow instead of assigning an empty
string; keep the existing addEventListener/removeEventListener logic with
handleKeyDown so the original overflow state is preserved across
mounts/unmounts.
apps/docs/src/components/examples/InteractiveCalendar.tsx (1)

536-544: 💤 Low value

Drag handle span should be a button for keyboard accessibility.

The drag handle uses a <span> element which is not keyboard-focusable by default. While @dnd-kit provides some accessibility attributes via {...attributes} {...listeners}, screen reader users may have difficulty using this control.

Consider adding role="button" and tabIndex={0} for better accessibility, or verify that @dnd-kit's attributes handle this.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/docs/src/components/examples/InteractiveCalendar.tsx` around lines 536 -
544, Replace the non-focusable span activator used for the drag handle with an
accessible control: update the span that uses setActivatorNodeRef,
{...attributes} and {...listeners} (the element that renders GripVertical) to be
keyboard-focusable and announced as a button—either change it to a <button>
element or add role="button" and tabIndex={0} and ensure the existing
{...attributes} and {...listeners} remain applied; verify focus styles and that
setActivatorNodeRef still receives the DOM node so `@dnd-kit` activation works
correctly.

@eluce2 eluce2 merged commit cd39d69 into main May 4, 2026
12 of 13 checks passed
@eluce2 eluce2 deleted the feature/docs-landing-hero-header branch May 4, 2026 19: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.

2 participants