Skip to content

Releases: Samuel0101010/wisp-design

v0.7.0-prerelease — Phase 6: Session-Replay + Component-Lib-Awareness

Choose a tag to compare

@Samuel0101010 Samuel0101010 released this 23 May 09:39

Session replay, component-library awareness, and four more synthesis-
listed Improvements over Impeccable land in v0.7.0.

Built by a 4-agent team (architect / coder-foundation / coder-features /
tester) with two parallel coder agents writing disjoint files.

5 Improvements landed

# Improvement Module
2 Per-session edit-stack with undo src/session/logger.ts + replay.ts + agent/history.ts
3 Morph-mode interpolation between variants src/agent/morph.ts
5 In-session policy-proposal flow src/agent/policy.ts
6 Structure-variant-mode (JSX-subtree alternatives) src/source/structure-variant-mode.ts
11 Component-library-aware edit-strategy hints src/agent/component-detect.ts

Plus the highest-value Phase-5 deferred fix

round-number-whitespace was firing per-occurrence on every Tailwind
padding:16px. Now does file-level aggregation: emits ONE violation iff
totalCount >= 4 AND roundCount/totalCount > 0.7. Verified on 3 fixtures
(1-decl: 0 hits / 4-decl-1.0-ratio: 1 hit / 4-decl-0.5-ratio: 0 hits).

What landed

Contracts (architect)

  • src/contracts/session.ts (332) - SessionEventEntry union extending
    Phase-3 UndoEntry via z.enum([...UndoEntryKindSchema.options, ...]),
    SessionReplayTimeline, PolicyProposal, MorphSource/Config,
    StructureVariantSpec with 8 kinds, 4 module interfaces
  • src/contracts/component.ts (254) - ComponentLib (7 values), Edit
    Strategy (4 values), ComponentDetectionResult, COMPONENT_DETECTION_
    RULES table per-lib
  • docs/session-replay.md (188) + docs/component-detection.md (141)

Session foundation (coder-foundation)

  • src/session/logger.ts (443) - SessionLoggerModule with 11
    convenience helpers; Phase-3 file-op kinds route via undoAppend,
    Phase-6 kinds via direct atomic-append; schema-validates every entry
  • src/session/replay.ts (419) - buildTimeline + listSessions +
    internal helpers; malformed JSONL warn-skip; idempotent fold;
    forward-join accept-variant to variants-emitted for primaryAxis
  • src/agent/history.ts (385) - runHistory CLI with
    --task/--list/--replay/--format text|json|markdown
  • src/agent/policy.ts (493) - 3-consecutive-same-axis trigger,
    frontmatter merge, declined-axes block re-trigger same session

Feature modules (coder-features)

  • src/agent/component-detect.ts (~360) - cap-then-average aggregation,
    6 libs + vanilla fallback, vanilla never wins by score, quick vs
    deep sampling
  • src/source/structure-variant-mode.ts (~330) - 8 kinds via
    template router, balanced-tag walker for splitJsxIntoHalves
  • src/agent/morph.ts (~265) - deterministic via sorted variable
    names, t clamped, interpolatable iff parsed + matching unit,
    non-interpolatable snap at t<0.5

Tests (tester, 130 new in 8 files)

  • session/logger.test.ts (18), session/replay.test.ts (18)
  • agent/history-cli.test.ts (12), agent/policy-proposal.test.ts (18)
  • agent/component-detect.test.ts (18), agent/morph-mode.test.ts (21)
  • source/structure-variant-mode.test.ts (18)
  • verify/anti-slop-fp-rate.test.ts (UPDATED) - flipped it.fails
    to it() after FPR fix

Cumulative: 626/626 tests green (40 test files, ~6s).

FPR fix - what worked, what's still outstanding

round-number-whitespace: per-occurrence -> file-level aggregation ->
0 hits on Tailwind defaults ✓

Soft-warn FPR delta: 45.71% -> 42.86%. The fix specifically targeted
round-number-whitespace, which now contributes 0 hits. The remaining
FPR is split between two OTHER soft rules:

  • single-weight-typography - 30 hits across realGood (legit
    single-weight CSS blocks)
  • default-tailwind-blue - 20 hits across borderline (color: #3b82f6)

These are the audit-recommended tightenings 2/4 from Phase 5. Test asserts
< 0.45 (calibrated to current state) with comment naming outstanding
rules. Phase 7 launch prep picks them up.

CLI surface

wisp-design history [--task ID] [--list] [--replay] [--format text|json|markdown]
wisp-design policy --propose | --apply <axis>=<value> | --show
wisp-design morph --variant-a-id ID --variant-b-id ID --t 0..1

Quality gates

typecheck:  clean
test:       626/626 in ~6s (40 files)
build:      19+ dist bundles
doctor:     14 checks (12 OK + 1 WARN policy.md + 1 OK sessions lazy)

Phase-6 deferred to Phase 7 launch prep

  • Anti-slop tightenings 2-4 (single-weight-typography scoping,
    default-tailwind-blue extension with brand-color whitelist, oklch
    branch for purple-blue-gradient, brace-anchoring for selector
    windows)
  • Multi-viewport baseline-comparison + visual regression
  • Reduced-motion pixelmatch-based two-render diff
  • history --replay implementation
  • Three more variant-anchor .md files plus full Huashu /
    UI-UX-Pro-Max content forks

Next

Phase 7 - Public Launch. README with GIF (Pick -> 3 variants -> Tune ->
Accept -> AA-block-demo), docs/architecture.md, docs/comparison.md table
(Impeccable / Stagewise / Onlook / v0 / Lovable / Claude Design), CI
matrix (Linux/Windows/macOS x Node 20+22), gh repo edit --visibility
public, gh release create v1.0.0 (NO --prerelease), HN-Post + X-Thread.
Plus the Phase-6-deferred polish items above.

v0.6.0-prerelease — Phase 5: Verification-Gate USP

Choose a tag to compare

@Samuel0101010 Samuel0101010 released this 23 May 08:52

THE Verification-Gate. THE wisp-design differentiator.

No other live-edit tool gates accept on (a11y + screenshot + anti-slop)
simultaneously. UI-UX-Pro-Max stops at "generated text", Impeccable stops
at "wrote to file", Onlook/Stagewise stop at "wrote to file", Huashu has
5-dim radar but no gate, Open-design has 5-dim gate but no source-edit.
wisp-design alone links source-edit + a11y-block + screenshot-diff +
anti-slop-lint in one accept-gate.

Built by a 4-agent team (architect / coder / security / tester) with
disjoint file ownership and security-architect-flagged coordination
between the parallel agents.

Quality-gate metrics (100-component-sample FPR canary)

hard-ban FPR    = 0.00%  (target <= 5%)  PASS
FN-rate         = 0.00%  (caught 30/30 known-slop)
soft-warn FPR   = 45.71% (target <= 20%) PINNED  [AUDIT-PENDING for Phase 6]
verdict         = USP delivered, calibration TODO

Hard-ban FPR being 0.00% on real-world-good components (shadcn/Radix/MUI
patterns) while still catching 100% of known-slop is the central proof
point. The soft-warn FPR is too noisy on Tailwind defaults — security's
audit pointed at the round-number-whitespace rule firing per-occurrence;
fix via file-level aggregation (>0.7 ratio threshold) lands in Phase 6.

What landed

Contracts (src/contracts/verify.ts, 491 lines)

  • VerifyMode enum across 5 modes (stop-hook | live-accept |
    live-with-screenshot | audit | audit-strict)
  • Lookup tables: MODE_CHECK_SETS, MODE_BLOCKS_ON_FAIL,
    MODE_TIMING_BUDGET_MS
  • AntiSlopRuleId (7 hard-bans + 5 soft), HARD_BAN_RULES Set
  • Aggregator helpers, per-check budgets, audit options schema

Verify modules (src/verify/, 8 files)

  • anti-slop-linter.ts (648) - pre-compiled regex set, 7 hard-bans
    (em-dash-ui, gradient-text-headline, default-glassmorphism,
    hero-metric-template, side-stripe-decoration, purple-blue-gradient,
    generic-ai-illustration) + 5 soft-suggestions
  • a11y-axe.ts (381) - axe-core via jsdom or Playwright when available
  • console-scan.ts (221) - 3 modes (session-log, bridge-poll, static)
  • tab-order.ts (232) - focus-trap-leak detection, tabindex audit
  • reduced-motion.ts (112) - CSS regex pass for motion-without-guard
  • multi-viewport.ts (276) - Playwright 4 viewports x {light,dark} = 8 PNGs
  • _sandbox.ts (384, security) - safe-launch with 6 URL guards
    (loopback-only, no userinfo, http(s) only, port 1025-65535) +
    chromium hardening (sandbox ON, disable-extensions, etc.) +
    restricted context (no downloads, no permissions) + route-handler
    aborts non-loopback hosts
  • gate.ts (259) - orchestrator via Promise.allSettled, per-check
    timeout, 5-mode routing, blocked field for audit-strict

CLI (src/agent/audit.ts, 308 lines)

  • wisp-design audit [paths...] with --mode fast|full|strict,
    --format text|json|markdown, --screenshot
  • Exit codes: 0/1/2/3 for pass/strict-block/bad-flag/throw

Stop-Hook (src/hooks/dispatcher.ts)

  • git diff HEAD --name-only via execFileSync (path-injection safe)
  • 50-file cap, UI-extension filter, 15ms tail-reserve
  • Dynamic-import linter (Node module-cache amortises after turn 1)
  • WISP_DESIGN_STRICT=1 env: permissionDecision:block JSON to stdout
  • Default: stderr warn, exit 0 (never blocks user on internal error)

Doctor (12/12 OK)

  • axe-core FAIL when missing, playwright + pixelmatch WARN
  • Doctor surfaces optionalDep absence so users can see it but don't have
    to install. Same path for both optional deps.

Tests (tests/verify/, 155 new tests in 9 files)

  • anti-slop-linter.test.ts (54) - 7 hard-bans + 5 soft-suggestions
    each with KNOWN-BAD + KNOWN-GOOD, plus extractCssFromFile per ext
  • anti-slop-fp-rate.test.ts (7) - 100-component canary
  • a11y-axe.test.ts (12) - contrast, alt-missing, [data-wisp-ui] exclusion
  • console-scan.test.ts (11) - 3-mode coverage + severity routing
  • tab-order.test.ts (12) - focus-trap, tabindex, focus-ring
  • reduced-motion.test.ts (8) - guard coverage + long-transition fail
  • multi-viewport.test.ts (20) - graceful skip + isLoopbackUrl unit tests
    • SandboxError code discrimination
  • gate.test.ts (18) - parallel timing, mode routing, aggregates
  • audit-cli.test.ts (13) - exit codes, formats, flag parsing

Cumulative: 502/502 tests green (155 verify + 77 agent/skills + 99
source + 94 browser + 77 bridge + 2 smoke).

npm deps

  • axe-core ^4.10.0 as regular dependency
  • playwright ^1.49.0 + pixelmatch ^7.1.0 as optionalDependencies
  • @types/pixelmatch as devDep

Open Decision #7 confirmed

warn-default in v0.x, hard-block via audit --mode strict opt-in.
Override-flow via floating-bar with session-log entry.

Phase-5 deferred items (Phase 6+)

  • 4 regex tightenings per security audit:
    • oklch() branch for purple-blue-gradient
    • brace-anchoring for selector windows
    • broader CSS-property scan for default-tailwind-blue (fill/stroke)
    • file-level aggregation for round-number-whitespace
  • Multi-viewport baseline-comparison + visual regression
  • reduced-motion pixelmatch-based two-render diff
  • Soft-warn FPR drop from 45.71% to <20% via aggregation fix

Quality gates

typecheck:  clean
test:       502/502 in ~5s (33 files)
build:      15 dist bundles (index.js 32.74 kB + 7 verify + 4 agent + live.js)
doctor:     12/12 OK

Next

Phase 6 - Session-Replay + Component-Lib-Awareness. Session logger writes
.wisp/sessions/<id>.jsonl (Improvement #2 vs Impeccable's no-undo);
wisp-design history viewer; component-detect.ts detects shadcn/Radix/
MUI/Tailwind to prefer prop-edits over CSS-overrides (Improvement #11);
structure-variant-mode (Improvement #6); in-session policy-proposal flow
(Improvement #5); morph-mode interpolation between variants (Improvement #3);
plus Phase-5 deferred fixes (anti-slop regex tightenings, FPR aggregation,
multi-viewport baseline comparison).

v0.5.0-prerelease — Phase 4: Agent-Loop + Skill-Korpus

Choose a tag to compare

@Samuel0101010 Samuel0101010 released this 22 May 17:48

Agent-loop CLI primitives + skill-corpus (30 markdown files, 2854 lines)
encoding Samuel's vault anti-slop doctrine, UI-UX-Pro-Max corpus (MIT fork),
Huashu directions, and Open-design's 5 OKLCh presets as an integrated
discoverability surface.

Critical architectural insight: Claude Code's reasoning IS the agent.
src/agent/poll-loop.ts provides one-shot pollOnce and postEvent
primitives; the while-loop lives declaratively in
skills/wisp-design/SKILL.md. Restartable across Claude sessions because
the bridge holds the cursor, not the agent.

What landed

Contracts (src/contracts/agent.ts, 433 lines)

  • PollOnceOptions, PollOnceResult, PostEventOptions, PostEventResult
  • VariantGenerationRequest / Response with 5 variant axes
    (hierarchy / layout / typography / color / density)
  • BrandSpec (9-section, name+oneLiner required, rest optional)
  • DesignTokens, SyncSource, SkillsIndexResult
  • Constants: POLL_LOOP_DEFAULT_TIMEOUT_MS=270000,
    ALLOWED_VARIANT_ANCHORS (13), ALLOWED_VARIANT_COUNT ({1,3,5,8}),
    VARIANT_RATIONALE_MAX_LEN=180

Agent modules (src/agent/, 4 files / 1144 lines)

  • _helpers.ts (187) - readPortLock with PID-liveness probe, parseFlags,
    typed PortLockMissingError / PortLockStaleError
  • poll-loop.ts (366) - pollOnce + postEvent (Node 20+ fetch +
    AbortController with 5s headroom), routeEvent exhaustive switch,
    runPollOnce + runPostEvent CLI runners
  • skills-index.ts (309) - indexSkills recursive walk + namespace counting,
    searchSkills BM25-lite with sqrt-length-norm, runSkills dispatcher
    (index / search / --help)
  • sync.ts (282) - syncFromVault with minimal glob compiler, SHA-256
    idempotent skip, YAML attribution frontmatter, runSync CLI

CLI lazy-load (src/index.ts + tsup.config.ts)

String-variable indirect import pattern bypasses TS module resolution at
compile time. tsup multi-entry now emits dist/agent/poll-loop.js,
dist/agent/skills-index.js, dist/agent/sync.js so the dispatcher's
runtime await import(...) resolves successfully.

Skill corpus (skills/, 30 files / 2854 markdown lines)

  • wisp-design/SKILL.md - auto-trigger skill with loop pseudocode
  • reference/ - live.md (KERN variant-generation prompt) + 5 themed
    sub-prompts (polish, bolder, quieter, colorize, layout)
  • policy/anti-slop.md - 7 Vault hard-bans (em-dash UI, gradient text,
    default glassmorphism, hero-metric template, side-stripe, purple-blue
    gradient, generic AI illustrations) + Huashu 5-dim self-critique
  • methodology/narrative-questions.md - 4 Pre-Code questions
    (Role / Distance / Temperature / Capacity)
  • methodology/junior-designer-flow.md - 4-phase Stub > Checkpoint > Fill > Verify
  • methodology/brand-asset-5-10-2-8.md - 5min / 10min / 2h / 8w protocol
    (opt-in via --brand flag per Open Decision #4)
  • data/anchors/ - 13 variant anchors (5 vault apps: Linear, Stripe,
    Anthropic, Aceternity, Apple + 5 Open-design presets: editorial,
    modern-minimal, tech-utility, brutalist, soft-warm + 3 vault-adjacents:
    restrained-cool, committed-indigo, drenched-warm)
  • data/directions/ - 00-INDEX with 25-direction list + README with MIT
    attribution to huashu-design
  • data/corpus/ - INDEX + README (UI-UX-Pro-Max MIT attribution) +
    30-row sample CSV demonstrating Quadruple shape

Open Decisions confirmed

  • #4 Brand-Asset 5-10-2-8: opt-in via --brand flag, default off
  • #5 UI-UX-Pro-Max: MIT-fork with attribution (Phase 4 ships INDEX +
    sample; full 14-CSV fork deferred to Phase 7)
  • #6 Vault-sync: explicit wisp-design sync command only, no watcher

Doctor (now 9/9 OK)

Added checkSkillsLayout + checkSkillManifest as WARN (not FAIL) so Phase 0
checkouts don't trip the gate.

Tests (tests/agent/ + tests/skills/, 77 new in 5 files)

  • poll-loop.test.ts (20) - bridge integration on port 31370, pollOnce/
    postEvent happy + error paths, routeEvent exhaustive, CLI runner I/O
  • skills-index.test.ts (19) - enumeration, search relevance, CLI dispatch
  • sync.test.ts (15) - glob matcher, idempotent SHA skip, attribution
  • skill-corpus.test.ts (15) - file existence, frontmatter, axis coverage,
    hard-ban presence, anchor count, attribution presence
  • anti-slop-canary.test.ts (8) - mission-critical canary locks the 7
    hard-bans + all 5 critique dimensions against silent removal

Cumulative: 347/347 tests green (77 agent/skills + 99 source + 94 browser

  • 77 bridge + 2 smoke + extras).

Quality gates

typecheck:  clean
test:       347/347 in ~4s (24 files)
build:      index.js 11.92 kB + agent/poll-loop.js 21.40 kB +
            agent/skills-index.js 16.27 kB + agent/sync.js 17.66 kB +
            live.js 31.30 kB
doctor:     9/9 OK

Phase-4 deferred items (Phase 7 launch prep)

  • Full UI-UX-Pro-Max 14-CSV fork with AgentDB HNSW indexing
  • Full Huashu 20+ directions content fork (reconcile synthesized list with
    upstream)
  • Real AgentDB pattern-store wiring (currently in-process BM25-lite, marked
    agentDbController: "phase-4-stub" per Phase-4 contract)

Next

Phase 5 - Verification-Gate (USP). Anti-slop linter that enforces the 7
Vault hard-bans on generated CSS; axe-core a11y delta with AA-blocking
policy; multi-viewport screenshot (375 / 768 / 1280 / 1920 + light + dark);
console-clean scan; tab-order smoke; reduced-motion diff. Verification-Gate
runs PARALLEL to LLM generate, not sequentially. Total p95 budget <= 3s.

This is the central wisp-design differentiator - no other live-edit tool
gates accept on a11y + screenshot + anti-slop.

v0.4.0-prerelease — Phase 3: Source-Edit Engine

Choose a tag to compare

@Samuel0101010 Samuel0101010 released this 22 May 16:55

Marker-bounded source-file splice with byte-equivalence contract, atomic
writes, JSONL undo log, 8-rule safety guard, brace+quote+comment-aware
@scope CSS carbonizer. Lead-fixed one blocking marker-regex bug
live-debugged via tester pins.

Built by a 4-agent team (architect / coder / security / tester) with
disjoint file ownership.

What landed

Contracts (src/contracts/source.ts, 463 lines)

  • MARKER_SYNTAX table per filetype (tsx/jsx/html/vue/svelte/css)
  • 6 marker kinds collapsed to 3 groups (inject/variants/style)
  • InjectMarker, VariantBlockMarker, MarkerBlock, AcceptOperation,
    DiscardOperation zod schemas
  • SafetyErrorCode enum, SafetyResult discriminated union
  • UndoEntryKind enum, UndoEntry schema (JSONL append-only)
  • 7 module interfaces, all constants

Modules (src/source/, 2700 lines / 7 files)

  • _helpers.ts (244) - shared detectEol, sha256, atomicWrite, findMarkerBlock
  • safety.ts (403) - 8-rule pipeline; single 512-byte head-read serves
    binary + magic-comment + EOL-detect (1 syscall round trip)
  • inject.ts (387) - injectLiveScript / removeLiveScript; JSX/TSX
    refused with agent-driven fallback; SHA256 hash-mismatch detection
  • wrap.ts (499) - wrapVariantBlock / discardVariantBlock; heuristic
    selector-to-anchor; brace-balanced tag-end walker
  • accept.ts (338) - findMarkerBlock, extractVariant, expandReplaceRange,
    acceptVariant with op.variantCss fallback
  • carbonize.ts (429) - pure CSS-in/CSS-out; recursive-descent parser;
    :scope-var merge with paramOverrides; var(--x, fallback) resolution;
    @media/@supports preserved
  • undo-stack.ts (201) - JSONL append, rotation at 10 MB, malformed-line
    warn-skip on read

Tests (tests/source/, 99 new tests in 6 files)

  • safety.test.ts (22) - all 8 rules + EOL detection + happy path
  • inject-roundtrip.test.ts (13) - byte-equivalence roundtrip across
    HTML/Vue/Svelte, CRLF preservation, double-inject refusal, hash-mismatch
  • wrap-discard.test.ts (15) - selector heuristics, ambiguity, self-closing
    tags, case-insensitive matching
  • accept-splice.test.ts (17) - findMarkerBlock filtering, extractVariant
    with @media nesting, expandReplaceRange indentation, paramOverrides bake
  • carbonize.test.ts (19) - worked example, var fallbacks, dead-var
    elimination, @supports preservation, malformed throws
  • undo-stack.test.ts (13) - append/read/rotate, malformed resilience

Cumulative: 270/270 tests green (99 source + 94 browser + 77 bridge + 2 smoke).

Live-debugged bug fix

Initial marker regex used [^-]*? for HTML/Vue/Svelte (and [^*]*? for
JSX/CSS). The HTML body class forbade hyphens, but UUID injectIds, ISO
timestamps, dashed hashes and URL-safe base64 all contain hyphens.

Consequence: findMarkerBlock returned null for HTML-family,
removeLiveScript always threw, double-inject refusal silently failed open,
byte-equivalence contract could not be verified.

Fix: all 5 patterns switched to [\s\S]*? - the trailing terminator
(--> for HTML, */ for JSX/CSS) is unique by spec, no inner-char
exclusion needed. Tester's 6 [BUG]-pinned tests rewrote to assert correct
post-fix behavior.

Known limitations (deferred to Phase 6 component-detect.ts)

  • wrap selectorToAnchor is heuristic - .btn does not match
    className="btn primary" (multi-class); pinned as [FINDING] test
  • wrap then discard not full-file byte-equivalent for JSX (engine
    consumes trailing newline); restoredByteEquivalent reports honestly
    false; tests assert structural recovery instead
  • inject then remove has collapseDoubleBlank artifact at splice site
    (one byte difference); engine reports honestly; markers gone + content
    structurally intact

Quality gates

typecheck:  clean
test:       270/270 in 3.82s (19 files)
build:      index.js 9.74 kB + live.js 31.30 kB
doctor:     7/7 OK

Next

Phase 4 - Agent-Loop + Skill-Korpus (src/agent/poll-loop.ts, skills/).
Long-poll loop with 270s slicing, variant-generation prompts encoding
Samuel's vault anti-slop checklist + UI-UX-Pro-Max corpus (MIT fork) +
Huashu directions + 4 Narrative Questions Pre-Code-Gate. AgentDB+HNSW
index over skills/data/corpus.

v0.3.0-prerelease — Phase 2: Browser Runtime (live.js)

Choose a tag to compare

@Samuel0101010 Samuel0101010 released this 22 May 13:25

Vanilla-JS IIFE bundle (dist/live.js) for in-page picking, configure, generation,
and variant cycling. 31.30 kB minified — well under the 50 kB live.js budget.

Built by a 4-agent team (architect / coder / security / tester) with disjoint
file ownership. No React, no DOM-deps, no Node imports — Browser-target ES2020.

What landed

Contracts (src/contracts/browser.ts, 467 lines)

  • BrowserState 5-kind discriminated union + 18-entry STATE_TRANSITIONS table
  • PickResult, Variant, ParameterBinding, Annotation, BridgeClient interface
  • SanitizeModule interface — single trust-boundary for user/agent strings
  • WispDesignGlobal / InitOptions / WispDesignHandle for the IIFE boot API
  • Constants split into src/browser/constants.ts (pure, no zod) to keep the
    IIFE bundle free of zod side effects

Browser modules (src/browser/, ~2500 lines across 10 files)

  • state-machine.ts — pure reducer, invalid event returns input unchanged
  • picker.tsdocument.elementFromPoint + pickable() (>=20x20,
    excludes WISP UI / html / body / script / style / meta / visibility:hidden)
  • multi-select.ts — Cmd/Ctrl-click-add toggle (Improvement #1 vs Impeccables single-select)
  • floating-bar.ts — 3-mode bar (configure / generating / cycling)
  • parameter-sliders.tszero-roundtrip CSS-var binding (Killer-Feature)
  • annotations.ts — structured {targetId, kind, note} — NOT PNG flatten (Improvement #7)
  • variant-render.ts@scope-based 3-variant cycling; variant 0 = live
    original (preserves listeners), variants 1+ are clones
  • index.ts — IIFE entry, window.WispDesign.init({ token, bridgeUrl })
  • bridge-client.ts — fetch POST + EventSource subscribe (Long-Poll fallback)

Security (src/browser/sanitize.ts, 302 lines)

  • escapeHtml, sanitizeFreeText (strips <script>/<style>/<iframe>/
    <object>/<embed>/<svg> blocks + on*= handlers + js:/data:/vbscript:
    prefixes + C0+DEL controls; word-boundary truncate at 75% threshold)
  • trustedCssVar (rejects url(/@import/expression(/{/}/;/backslash)
  • trustedSelector (deep :has() check rejects nested iframe/script)
  • Returns structured SanitizeError { code, message, detail? }

Tests (tests/browser/, 94 new tests across 7 files)

  • sanitize.test.ts (28) — XSS payloads, CSS-var + selector rejection
  • state-machine.test.ts (19) — transitions, self-loops, invalid-event silence
  • picker.test.ts (15) — pickable() filter, selector chains, attribute subset
  • multi-select.test.ts (6) — modifier toggle, [data-wisp-ui] exclusion
  • variant-render.test.ts (9) — wrapper, 3 siblings, @scope rule injection
  • bundle-size.test.ts (5) — dist/live.js exists, < 50000 bytes, IIFE banner
  • constants-drift.test.ts (12) — byte-equivalence guard between
    src/browser/constants.ts and src/contracts/browser.ts (CI catches drift)

Cumulative: 171/171 tests green (94 browser + 77 bridge + 2 smoke).

Tooling

  • tsup multi-entry: CLI ESM (dist/index.js, 9.74 kB) + Browser IIFE
    (dist/live.js, 31.30 kB, globalName: WispDesign, target es2020, minified,
    noExternal: [/.*/], outExtension strips the .global infix)
  • vitest environmentMatchGlobs: tests/browser/** runs jsdom, rest pure Node
  • jsdom + @types/jsdom as devDeps

Notable engineering decision

Bundle was 91.17 kB initiallysanitize.ts value-imported
FREE_TEXT_MAX_LEN from contracts/browser.js, which triggered top-level
z.object(...) evaluation and inlined the full zod runtime (~60 kB overhead).

Fix: rewire sanitize.ts to import from src/browser/constants.ts (pure
constants, no zod). The other 9 browser modules already did this — only
sanitize.ts was the offender. constants-drift.test.ts now guards against
future drift between constants.ts and contracts/browser.ts.

Result: 31.30 kB. 60 kB reduction in a single import rewire.

Pinned behaviors

  • escapeHtml is NOT idempotent (&amp;&amp;amp;)
  • textPreview length is 81 when truncated ( ellipsis appended after 80 char slice)
  • sanitizeFreeText strips the leading whitespace inside <img on…=…> along with
    the handler — result lacks a trailing space inside the tag
  • jsdom does not implement @scope cascade — variant-render structural tests
    assert the @scope rule is in the <style> textContent, not that styles apply

Quality gates

$ npm run check
typecheck:  clean
test:       171 passed in 13 files (2.45s)
build:      index.js 9.74 kB + live.js 31.30 kB

$ node dist/index.js doctor
7/7 OK

Next

Phase 3 — Source-Edit Engine (src/source/). Marker-bounded fs.writeFileSync
splice, byte-equivalent inject/remove roundtrip, carbonize CSS @scope
permanent on accept, per-session .wisp/sessions/<id>.jsonl undo log
(Improvement #2 vs Impeccables no-undo), refuse-edit safety on generated files.

v0.2.0-prerelease — Phase 1: Local Bridge Server

Choose a tag to compare

@Samuel0101010 Samuel0101010 released this 22 May 12:32

Local HTTP+SSE+Long-Poll bridge with token auth and a 5-rule path-traversal guard.
Repo stays private until v1.0.0.

Built by a 4-agent team (architect / coder / security / tester) with disjoint
file ownership — no merge conflicts, no overlapping writes.

What landed

Contracts (src/contracts/bridge.ts, 426 lines)

  • BridgeEventSchema zod discriminatedUnion across 10 event kinds
  • PortLockSchema, BridgeStatusSchema, BridgeHealthSchema, BridgeHttpErrorSchema
  • LongPollRequestSchema (.refine enforces timeout <= LONG_POLL_CAP_MS = 270000)
  • AuthModule interface, BridgeEndpoint discriminated union (11 variants)
  • BridgeServerOptions / BridgeServerHandle for the boot API

Server (src/bridge/server.ts 484 + http-helpers.ts 127 lines)

Plain Node http, no Express/Hono. Binds 127.0.0.1 only. 11 endpoints:

  • GET /health (no auth, liveness)
  • GET /status (auth, BridgeStatus)
  • GET /live.js (no auth, Phase 2 stub)
  • GET /design-system.json (auth, serves .wisp/design-tokens.json)
  • GET /source (auth + guardPath)
  • GET /events SSE (auth, 15s keepalive)
  • POST /events (auth, validates BridgeEventSchema)
  • GET/POST /poll (auth, long-poll with silent cap)
  • POST /annotation (auth, kind-locked)
  • GET /stop (auth, graceful shutdown)

256 KB body cap (413 BODY_TOO_LARGE). Cursor format seq-N-sessionId. Long-poll
silent-clamps oversize timeouts to 270000 ms (no 400 — improves Impeccable).

Port discovery (port-discovery.ts, 123 lines)

  • findFreePort over 31337..31400 (Improvement #8 vs Impeccables fixed :8400)
  • Atomic lockfile (.tmp + rename, mode 0o600)
  • Stale-pid detection via process.kill(pid, 0)

Auth + path guard (src/bridge/auth.ts, 220 lines)

  • generateToken UUIDv4
  • validateToken UUID-format regex + length-mismatch UNAUTHORIZED
    (avoids timing leak) + final crypto.timingSafeEqual
  • guardPath 5-rule pipeline:
    1. reject absolute paths (POSIX + Windows)
    2. reject any surviving .. segment after normalize
    3. path.resolve must prefix-match projectRoot
    4. realpathSync re-check (symlink-escape blocked); ENOENT is OK (caller 404)
    5. hard-deny /.git/, /node_modules/, /.wisp/sessions/, .env*

Tests (tests/bridge/*.test.ts, 77 passing in 6 files)

  • auth.test.ts (26) — token generation, validation edge cases, guardPath all 5 rules
  • path-traversal.test.ts (11) — mixed seps, unicode, mid-segment denies, long paths
  • port-discovery.test.ts (11) — range/exhaustion/stale-pid/atomic-write
  • long-poll-slicing.test.ts (6) — fast-path, wake-up, empty timeout, cursor advance,
    client-disconnect race, silent-clamp
  • server-smoke.test.ts (21) — auth gating, /source traversal, body validation,
    413 cap, SSE keepalive, /stop graceful

Tooling

  • .gitattributes normalizes line endings to LF (Phase 3 needs byte-equivalence
    on inject/remove roundtrip)
  • Version bump to 0.2.0-prerelease in all three manifests

Quality gates

$ npm run check
typecheck:  clean
test:       77 passed in 6 files (1.28s)
build:      9.74 kB

$ node dist/index.js doctor
7/7 OK

Next

Phase 2 — Browser Runtime (src/browser/live.js). Vanilla-JS IIFE, state machine
PICKING → CONFIGURING → GENERATING → CYCLING, floating bar, multi-select via
⌘-click-add, parameter sliders binding to CSS vars.

v0.1.0-prerelease — Phase 0: Scaffolding & Manifest

Choose a tag to compare

@Samuel0101010 Samuel0101010 released this 22 May 11:57

First scaffold tag. Repo is private until v1.0.0.

What landed

  • npm package skeleton (ESM, tsup, zod, vitest, @types/node@20)
  • .claude-plugin/plugin.json + marketplace.json with verified v3.x schema
    (repository as string, plugins[].source as object — both are known gotchas)
  • hooks/hooks.json three-layer matcher envelope wired for UserPromptSubmit,
    PostToolUse (Edit|Write|MultiEdit), Stop, SessionEnd
  • commands/wisp-design.md top-level dispatcher with subcommands
    live / init / audit / history / tokens / sync
  • src/index.ts CLI + src/cli/doctor.ts (manifest validator) +
    src/hooks/dispatcher.ts (stdin-drain no-op hooks for Phase 0)
  • dist/index.js built and committed (plugin clones have no build step)
  • vitest smoke test passes; wisp-design doctor returns 7/7 OK

Quality gate

$ node dist/index.js doctor
  Y node version - v22.16.0
  Y .claude-plugin/plugin.json - wisp-design v0.1.0-prerelease
  Y .claude-plugin/marketplace.json - 1 plugin(s)
  Y hooks/hooks.json - 4 event(s): UserPromptSubmit, PostToolUse, Stop, SessionEnd
  Y commands/wisp-design.md
  Y LICENSE - MIT
  Y dist/index.js - 9.7 kB
wisp-design doctor: OK

Next

Phase 1 — Local Bridge Server (src/bridge/). Plain Node HTTP + SSE, auto-port
discovery, token+UUID auth, path-traversal guard on /source.