Refresh docs landing page hero and header#233
Conversation
…/proofsh/proofkit into feature/docs-landing-hero-header
…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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (17)
📝 WalkthroughWalkthroughThis 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. ChangesTerminology Standardization
Documentation Restructure & New Guides
Marketing, Home Redesign & Layout
UI Components, Interactive Examples & Visuals
Config, scripts & minor repo hygiene
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
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 winUpdate the subsection count to match the added failure mode.
You added item
#6at Line 77, but the header still sayswebviewer-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 winDon’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 winFail 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 winHandle 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 winIcon-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 winAdd 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-onlylabel linked viahtmlFor/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 winUse 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.As per coding guidelines, "Use semantic HTML and ARIA attributes for accessibility ... Use semantic elements (``, ``, etc.) instead of divs with roles."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>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 winAdd 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 winLine 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 winAdd 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 winProvide 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 winAdd 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 winAdd accessible SVG labeling to fix CI and a11y compliance.
The root
<svg>has no non-empty<title>, which triggersbiome lint/a11y/noSvgWithoutTitleand 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 winMissing SVG
<title>causes accessibility lint failure.This component hits the same
a11y/noSvgWithoutTitleerror 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 winWordmark SVG needs a non-empty title for accessibility compliance.
The
<svg>on Line 4 is missing a<title>, so Biome flagsa11y/noSvgWithoutTitleand 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 winDark wordmark SVG is missing required accessible title metadata.
a11y/noSvgWithoutTitleis 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 winHorizontal logo dimensions look reversed.
Line 14 and Line 15 use
height={150}withwidth={96}forlogo-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 winAvoid static SVG gradient IDs in reusable component instances.
Multiple
ProofLogorenders 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 winAdd
noopeneron the external new-tab link.Line 95 should include
noopeneralongsidenoreferrerwhen usingtarget="_blank".Suggested fix
- rel="noreferrer" + rel="noopener noreferrer"As per coding guidelines "Add
rel=\"noopener\"when usingtarget=\"_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 winAdd a programmatic label for the notes textarea.
Line 205 has an unlabeled form control; add a
<label>oraria-labelso 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 winReplace shell-interpolated
execSynccalls with argument-safe process execution.Lines 71, 207, and 222 use shell command interpolation, creating command-injection risks. Switch to
execFileSyncwith 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 winAdd a programmatic label for the search input.
The input relies on placeholder text only; it needs an associated label for assistive technologies.
As per coding guidelines, "Use semantic HTML and ARIA attributes for accessibility: ... Add labels for form inputs".♿ 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} />🤖 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 winAssociate visible field text with inputs using real
<label>elements.
Fieldcurrently renders a<span>, so controls like Line 114, Line 125, and Line 200 are not programmatically labeled for assistive tech.As per coding guidelines, "Use semantic HTML and ARIA attributes for accessibility: ... Add labels for form inputs".♿ 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>🤖 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 winConsider 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 winExtract the chart scale multiplier into a named constant.
Line 122 uses
1.55inline; this is a magic number and makes tuning harder.As per coding guidelines, "Use meaningful variable names instead of magic numbers - extract constants with descriptive names."Suggested fix
+const REVENUE_BAR_HEIGHT_SCALE = 1.55; @@ - style={{ height: `${height * 1.55}px` }} + style={{ height: `${height * REVENUE_BAR_HEIGHT_SCALE}px` }}🤖 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 winAvoid 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.As per coding guidelines, "Use destructuring for object and array assignments."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> ))}🤖 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 winReplace hardcoded incoming total with a derived constant.
"126"is a magic number and can drift frominventoryItems.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 winType
dateRangefrom const-backed options for stronger safety.
dateRangeis currently a free-formstring. 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 winConsider extracting a shared base SVG component for dark/light variants.
codexDark.tsxandcodexLight.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 winAlign 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 valueConsider adding null guards for defensive coding.
The type assertions assume
ref.currentandcanvas.parentElementare 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 winPreserve 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 valueDrag 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-kitprovides some accessibility attributes via{...attributes} {...listeners}, screen reader users may have difficulty using this control.Consider adding
role="button"andtabIndex={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.
Summary by CodeRabbit
New Features
Documentation
Chores