@siteswarm/critique
@siteswarm/critique
Section titled “@siteswarm/critique”Headless in-situ visual critique, DOM-anchored click-to-comment client review engine, and multi-tenant persistence service for SiteSwarm applications.
Part of SiteSwarm’s automated editorial feedback, visual regression triage, and continuous design review pipeline.
Table of Contents
Section titled “Table of Contents”- Overview
- Architecture
- 3-State Lifecycle Machine
- Storage Adapters
- Deduplication Engine
- Subpath Exports
- Integration Guide (Astro)
- CLI Workflows & Operations
- Standard Quick Tags
- Database Schema (D1 Migration)
Overview
Section titled “Overview”@siteswarm/critique bridges the gap between deployed user interfaces and developer issue tracking. It allows designers, technical project managers, autonomous AI agents, and end users to visually inspect any staging or preview deployment, click directly on offending UI elements, tag design or copy flaws, and persist structured feedback.
Key design goals:
- Zero Framework Lock-in: Client overlay runs as vanilla TypeScript with isolated DOM rendering; server endpoints work in Cloudflare Workers, Astro, Node.js, and standard Fetch API runtimes.
- Resilient Anchoring: Elements are identified via deterministic DOM paths rather than fragile pixel coordinates.
- Zero Untracked Feedback: Feedback cycles seamlessly through a 3-state machine (
open->addressed->resolved), linking commits and PRs directly to visual elements. - Multi-Tenant Edge Persistence: Supports Cloudflare D1 SQL, Cloudflare KV, local
.swarm/critiques/JSON files, and in-memory mock stores.
Architecture
Section titled “Architecture”DOM Selector Targeting vs Coordinate Pins
Section titled “DOM Selector Targeting vs Coordinate Pins”Traditional visual feedback widgets anchor comments to absolute (x, y) pixel coordinates or relative viewport percentages. In modern responsive web design, coordinate pins break immediately:
- Fluid typography and responsive layouts cause elements to shift positions across screen sizes.
- CSS Flexbox and Grid dynamically rearrange columns and cards between mobile, tablet, and desktop viewports.
- Asynchronous hydration and dynamic content insertions alter document flow.
- Window resizing or zooming misaligns pins with their intended target elements.
@siteswarm/critique uses DOM Selector Targeting:
- When a user clicks on an element in inspect mode, the overlay computes a deterministic CSS selector:
- Prefer explicit IDs (
#hero-title). - Prefer test IDs (
[data-testid="cta-button"]). - Fall back to a semantic ancestor path (
section.masthead > div.container > h1).
- Prefer explicit IDs (
- The overlay extracts the element’s actual inner text snippet (
textSnippet) and nearby component boundary. - During rendering, pins dynamically compute their bounding client rect from
document.querySelector(c.selector), ensuring the pin stays locked to the element during responsive resize, scroll, and viewport modifications. If an element is re-rendered or moved within the DOM, the feedback remains precisely pinned to it.
┌────────────────────────────────────────────────────────┐│ Client Browser (Inspect Mode) ││ ││ [Element Clicked: <h1>"Lead Architect"</h1>] ││ │ ││ ▼ ││ Compute Target: ││ - Selector: "header.masthead > h1" ││ - Component: "<header.masthead>" ││ - Snippet: "Lead Architect" ││ - Tags: ["Too Buzzwordy", "Needs Restraint"] ││ - Comment: "Replace with Systems Engineer" │└───────────────────────┬────────────────────────────────┘ │ POST /api/critique ▼┌────────────────────────────────────────────────────────┐│ Server Handler (createCritiqueHandler) ││ - Zod Schema Validation (createCritiqueSchema) ││ - Semantic Deduplication Key Check ││ - Version Metadata Stamp (Commit SHA, Build Time) │└───────────────────────┬────────────────────────────────┘ │ ▼┌────────────────────────────────────────────────────────┐│ Storage Adapter (D1 / KV / File / Memory) ││ cap_critiques table or .swarm/critiques/<app>.json │└────────────────────────────────────────────────────────┘Component Boundary Inference
Section titled “Component Boundary Inference”The overlay automatically infers high-level component ownership by walking up the DOM tree looking for explicit contract attributes:
[data-component](e.g.data-component="ProjectShowcase")[data-critique-target](e.g.data-critique-target="PricingTable")- Semantic layout containers:
<section>,<header>,<footer>,<article>,<form>.
If no component attribute is found, it uses the element’s qualified tag and class name.
In-Situ Client Overlay
Section titled “In-Situ Client Overlay”The client overlay (CritiqueOverlay) mounts entirely within a isolated root container with namespaced CSS classes (ss-*) to prevent collision with application styles:
- Floating Launcher Pill: Bottom-right indicator showing active critique count and toggling inspect mode.
- Inspect HUD: Floating top bar during active inspection displaying keyboard tips (
ESCto exit) and state toggles. - Hover Highlighter: Real-time bounding box highlighting the hovered element and previewing its selector and component tag.
- Pin Overlay: Badged pins placed directly adjacent to target elements indicating open, addressed, or resolved status.
- Dock Drawer: Slide-over management panel listing all recorded critiques, supporting status updates, markdown report export, and one-click filtering.
3-State Lifecycle Machine
Section titled “3-State Lifecycle Machine”Critiques progress through an auditable 3-state machine designed to prevent untracked work and ensure changes are verified:
stateDiagram-v2 [*] --> open : User / Agent pins critique open --> addressed : Developer / Agent fixes in PR (records commit SHA, prNumber) addressed --> resolved : Verified on Staging / Merged (records commit SHA, resolvedAt) resolved --> open : Reopened (regression or incomplete fix) addressed --> open : Reopened (failed review) resolved --> [*] : Archivedopen:- Feedback is newly logged and unresolved.
- Pinned with amber visual badges in the client overlay.
addressed:- A developer or coding agent has implemented a fix in a feature branch or PR.
- Automatically records
addressedAtVersionmetadata (commitSha,prNumber,addressedAt). - Pinned with teal badges in the client overlay.
resolved:- The fix has been verified on staging or merged into production.
- Automatically records
resolvedAtVersionmetadata (commitSha,resolvedAt). - Pinned with green badges (or hidden when filtered).
- Can be batch-resolved via
pnpm swarm critique:resolve-addressedor POST/api/critique?action=resolve-addressedduring deployment pipelines.
- Reopen:
- Both
addressedandresolveditems can be transitioned back toopenat any time if regressions occur.
- Both
Storage Adapters
Section titled “Storage Adapters”All storage backends implement the CritiqueStorageAdapter interface:
export interface CritiqueStorageAdapter { list( tenantId: string, options?: { environment?: string; status?: string | string[] } ): Promise<Critique[]>; create(critique: Critique): Promise<Critique>; update(id: string, input: UpdateCritiqueInput): Promise<Critique | null>; resolveAddressed?(tenantId: string, options?: { commitSha?: string }): Promise<number>; clear?(tenantId?: string): Promise<void>;}Cloudflare D1 Adapter (D1CritiqueAdapter)
Section titled “Cloudflare D1 Adapter (D1CritiqueAdapter)”Production relational SQL storage built for Cloudflare D1:
- Stores records in table
cap_critiques. - Automatically maps between normalized
CritiqueRowdatabase records and typedCritiqueobjects. - Leverages SQL indexes on
(tenant_id, environment)andstatusfor sub-millisecond querying across large datasets. - Implements parameterized queries to prevent SQL injection.
import { D1CritiqueAdapter } from "@siteswarm/critique";
const adapter = new D1CritiqueAdapter(env.DB);Cloudflare KV Adapter (KVCritiqueAdapter)
Section titled “Cloudflare KV Adapter (KVCritiqueAdapter)”Fast global edge key-value storage:
- Stores critiques per tenant at key
critiques:<tenantId>. - Cross-Environment Transcendence: Critiques transcend ephemeral PR preview deployments and staging environments while allowing optional environment filtering.
- Auto-Healing: Transparently prunes duplicate records on read and migrates legacy environment-partitioned keys (
critiques:<tenantId>:<env>).
import { KVCritiqueAdapter } from "@siteswarm/critique";
const adapter = new KVCritiqueAdapter(env.CRITIQUES_KV);Local File Adapter (FileCritiqueAdapter)
Section titled “Local File Adapter (FileCritiqueAdapter)”Filesystem-backed persistence for local offline development and CLI tool inspection:
- Saves records to
.swarm/critiques/<tenantId>.json. - Zero database configuration required.
- Automatically handles directory creation, JSON serialization, and semantic deduplication.
import { FileCritiqueAdapter } from "@siteswarm/critique";
const adapter = new FileCritiqueAdapter(".swarm/critiques");In-Memory Adapter (MemoryCritiqueAdapter)
Section titled “In-Memory Adapter (MemoryCritiqueAdapter)”Ephemerally stored in a JavaScript Map:
- Zero setup, ideal for automated unit tests, CI test runners, and mock environments.
import { MemoryCritiqueAdapter } from "@siteswarm/critique";
const adapter = new MemoryCritiqueAdapter();Deduplication Engine
Section titled “Deduplication Engine”To prevent multiple reviewers or automated sweeps from creating duplicate entries for the same element, @siteswarm/critique includes deterministic content deduplication:
- ID Deduplication: Rejects or updates existing records matching an explicit critique
id. - Semantic Content Deduplication: Computes a deduplication fingerprint:
$$\text{key} = \text{norm}(\text{selector}) \mid \text{norm}(\text{textSnippet}) \mid \text{norm}(\text{comment}) \mid \text{sortedTags}$$
If an open or addressed critique with an identical fingerprint already exists,
POST /api/critiquereturns the existing critique with{ deduplicated: true }without inserting a redundant record. - Bulk Pruning:
deduplicateCritiques(items)is run automatically during adapter reads and writes to heal any existing duplicate records.
Subpath Exports
Section titled “Subpath Exports”The package exports dedicated bundles configured in package.json:
| Subpath | Description | Primary Exports |
|---|---|---|
. |
Universal server engine & core abstractions | createCritiqueHandler, D1CritiqueAdapter, KVCritiqueAdapter, FileCritiqueAdapter, MemoryCritiqueAdapter, deduplicateCritiques, createCritiqueSchema, updateCritiqueSchema, STANDARD_CRITIQUE_TAGS |
./client |
Browser overlay & client UI | mountCritiqueOverlay, CritiqueOverlay, formatCritiqueMarkdown, DEFAULT_QUICK_TAGS |
./schema |
D1 database tables & row mappers | CAPABILITY_TABLE_CRITIQUES, mapRowToCritique, mapCritiqueToRow, CritiqueRow |
Integration Guide (Astro)
Section titled “Integration Guide (Astro)”1. Server API Route
Section titled “1. Server API Route”Create src/pages/api/critique.ts in your Astro project to expose the REST API endpoints (GET, POST, PATCH):
import type { APIRoute } from "astro";import { createCritiqueHandler, D1CritiqueAdapter, KVCritiqueAdapter, FileCritiqueAdapter, MemoryCritiqueAdapter,} from "@siteswarm/critique";
export const prerender = false;
async function getHandler() { let cfEnv: Record<string, any> | undefined; try { // Cloudflare Workers runtime bindings const cfWorkers = await import("cloudflare:workers"); if (cfWorkers && cfWorkers.env) { cfEnv = cfWorkers.env; } } catch { // Node.js development runtime }
const env = cfEnv || (typeof process !== "undefined" ? process.env : {}); const environment = env.ENVIRONMENT || (import.meta.env.DEV ? "development" : "staging");
// Dynamic adapter resolution let storage; if (env.DB) { storage = new D1CritiqueAdapter(env.DB); } else if (env.CRITIQUES_KV || env.SESSION) { storage = new KVCritiqueAdapter(env.CRITIQUES_KV || env.SESSION); } else if (typeof process !== "undefined" && process.versions?.node) { storage = new FileCritiqueAdapter(); } else { storage = new MemoryCritiqueAdapter(); }
return createCritiqueHandler({ appId: "client-app", environment, serviceBinding: env.CRITIQUE_SERVICE, // Optional central platform service binding storage, });}
export const GET: APIRoute = async ({ request }) => { const handler = await getHandler(); return handler(request);};
export const POST: APIRoute = async ({ request }) => { const handler = await getHandler(); return handler(request);};
export const PATCH: APIRoute = async ({ request }) => { const handler = await getHandler(); return handler(request);};
export const ALL: APIRoute = async ({ request }) => { const handler = await getHandler(); return handler(request);};2. Client Overlay Component
Section titled “2. Client Overlay Component”Create src/components/CritiqueOverlay.astro and mount it within your main layout (e.g. src/layouts/Layout.astro):
<script> import { mountCritiqueOverlay } from "@siteswarm/critique/client";
if (typeof window !== "undefined") { const urlParams = new URLSearchParams(window.location.search);
// URL parameter overrides (?critique=1, ?critique=0) if (urlParams.get("critique") === "0" || urlParams.get("no-critique") === "1") { try { localStorage.setItem("critique_disabled", "true"); } catch {} } else if (urlParams.get("critique") === "1" || urlParams.get("critique") === "true") { try { localStorage.removeItem("critique_disabled"); } catch {} }
const isExplicitlyDisabled = localStorage.getItem("critique_disabled") === "true";
if (!isExplicitlyDisabled) { const isStaging = window.location.hostname.includes("staging") || window.location.hostname.includes("preview");
const overlay = mountCritiqueOverlay({ tenantId: "client-app", apiEndpoint: "/api/critique", environment: isStaging ? "staging" : "development", autoActivateQueryParam: "critique", });
// Browser console helper methods (window as any).disableCritique = () => { localStorage.setItem("critique_disabled", "true"); overlay?.destroy(); console.info("[SiteSwarm Critique] Disabled."); };
(window as any).enableCritique = () => { localStorage.removeItem("critique_disabled"); location.reload(); }; } }</script>3. Markdown Report Generation
Section titled “3. Markdown Report Generation”Export critiques recorded on a page into a formatted markdown audit report for issue templates or PR descriptions:
import { formatCritiqueMarkdown } from "@siteswarm/critique/client";import type { ClientCritiqueItem } from "@siteswarm/critique/client";
const report = formatCritiqueMarkdown(critiques, { tenantId: "client-app", environment: "staging", url: "https://staging.example.com",});
console.log(report);Sample output:
## 🔍 SiteSwarm Editorial Critique Report
- **Application**: `client-app`- **Environment**: `staging`- **Target URL**: https://staging.example.com- **Generated At**: 2026-10-10T22:00:00.000Z- **Total Feedback Items**: 1
### 1. header.masthead > h1 [OPEN]
- **Status**: `open`- **Selector**: `header.masthead > h1`- **Tags**: `[Too Buzzwordy]` `[Needs Restraint]`- **Target Snippet**: `Master Systems Engineer`- **Critique Note**: Replace with Software Engineer & Systems Builder.
---CLI Workflows & Operations
Section titled “CLI Workflows & Operations”All critiques can be inspected and managed from the command line using the @siteswarm/governance CLI (pnpm swarm critique:*):
1. List Open Critiques
Section titled “1. List Open Critiques”# List local critiques (.swarm/critiques/<app>.json)pnpm swarm critique --app me-portfolio
# Query live Cloudflare edge KV / D1pnpm swarm critique --app me-portfolio --remote
# Filter by environment or lifecycle statuspnpm swarm critique --app me-portfolio --env staging --status open2. Mark Critiques as Addressed
Section titled “2. Mark Critiques as Addressed”When you implement fixes for feedback items, mark them as addressed:
# Mark specific critique IDspnpm swarm critique:address --app me-portfolio --ids critique-1,critique-2
# Mark all open critiques for this application as addressedpnpm swarm critique:address --app me-portfolio --all3. Verify & Resolve Critiques
Section titled “3. Verify & Resolve Critiques”# Resolve verified critiquespnpm swarm critique:resolve --app me-portfolio --ids critique-1
# Batch-resolve all addressed critiques upon staging releasepnpm swarm critique:resolve-addressed --app me-portfolio4. Deduplicate Critique Records
Section titled “4. Deduplicate Critique Records”Prune redundant records across local files or edge storage:
pnpm swarm critique:dedup --app me-portfoliopnpm swarm critique:dedup --app me-portfolio --remoteStandard Quick Tags
Section titled “Standard Quick Tags”@siteswarm/critique comes with 6 predefined editorial quick tags configured in STANDARD_CRITIQUE_TAGS and DEFAULT_QUICK_TAGS:
| Tag | Intended Usage | Recommended Action |
|---|---|---|
Too Buzzwordy |
Jargon-heavy copy, exaggerated claims, or generic marketing prose | Rewrite with concrete technical verbs and specific achievements |
Remove Badge |
Cluttered visual chrome, excessive pill tags, or redundant badges | Strip superfluous pill elements to reduce visual noise |
Needs Restraint |
Over-styled layouts, aggressive gradients, or over-engineered motion | Simplify whitespace, typography, and container padding |
Love This |
Commendable aesthetic execution or high-impact technical copy | Keep as anchor pattern for other sections |
Formatting Issue |
Line height, clipping, overflow, or broken responsive margins | Adjust CSS layout rules and container flex/grid parameters |
Copy Edit |
Typo, punctuation mismatch, grammar fix, or outdated metadata | Direct text replacement in Markdown or CMS content |
Database Schema (D1 Migration)
Section titled “Database Schema (D1 Migration)”The initial D1 schema is defined in migrations/0001_initial.sql:
CREATE TABLE IF NOT EXISTS cap_critiques ( id TEXT PRIMARY KEY, tenant_id TEXT NOT NULL, environment TEXT NOT NULL, url TEXT NOT NULL, component_name TEXT, selector TEXT NOT NULL, text_snippet TEXT, tags TEXT NOT NULL DEFAULT '[]', comment TEXT, author_name TEXT, status TEXT NOT NULL DEFAULT 'open', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, version_metadata TEXT);
CREATE INDEX IF NOT EXISTS idx_cap_critiques_tenant_env ON cap_critiques(tenant_id, environment);
CREATE INDEX IF NOT EXISTS idx_cap_critiques_status ON cap_critiques(status);License
Section titled “License”MIT © SiteSwarm Fleet