Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: CoreBunch/Instatic
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.0.6
Choose a base ref
...
head repository: CoreBunch/Instatic
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.0.7
Choose a head ref
  • 5 commits
  • 123 files changed
  • 2 contributors

Commits on Jun 29, 2026

  1. feat(framework): import Core Framework defaults from onboarding (#91)

    * feat(framework): import Core Framework defaults from onboarding
    
    Add a Core Framework default-preset importer and wire it into the
    dashboard onboarding "framework" step.
    
    Engine (src/core/framework/coreFrameworkPreset.ts):
    - buildCoreFrameworkColorSettings() maps the Core Framework default color
      system (6 groups / 13 tokens — Brand, Background, Text, Base, Neutral,
      Status) into FrameworkColorToken[]: hsla values, dark-mode pairs,
      transparent steps, and per-token utility kinds from each token's `gen`
      list. Shades/tints are generated from a count (schema-native).
    - buildCoreFrameworkSettings({ includeUtilities }) returns a full
      FrameworkSettings (colors + typography + spacing + Core Framework
      preferences). Typography/spacing reuse the existing CF-mirroring
      defaults.
    
    Two import modes:
    - Full framework — every generated utility class plus :root variables;
      tree-shaking off so the whole utility set ships in framework.css.
    - Variables only — the same :root variables (base, shades, tints,
      transparent steps, scale clamps) with color utilities off and the
      typography/spacing class generators dropped.
    
    Onboarding UI:
    - FrameworkImportModal presents the two modes as role="radio" cards in
      the shared Dialog, loads the site, drops the built settings onto
      settings.framework, and saves the shell only (pages untouched).
    - The dashboard onboarding "framework" step opens the modal; the panel
      owns the modal so DashboardPage stays at its size budget.
    - useOnboardingState now exposes refresh() so the step flips to done
      after import.
    
    Adds FrameworkSettings to the framework schema (source of truth) and a
    §8.14 button-primitive allowlist entry for the radio cards.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * style(dashboard): refine framework-import card states and typography
    
    - Card backgrounds use overlay tints: idle --overlay-5, hover --overlay-10,
      active --overlay-20 (the previous active state relied on a box-shadow token
      used as a color, which rendered nothing).
    - Bigger, bolder, brighter text: titles --text-xl/700, descriptions
      --text-m/500 in --text, weight-500 lede.
    - Cleaner bullet list: mint accent checkmark icons instead of grey dots.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    DavidBabinec and claude authored Jun 29, 2026
    Configuration menu
    Copy the full SHA
    6f09654 View commit details
    Browse the repository at this point in the history
  2. fix(security): harden sanitizers and regexes flagged by CodeQL (#94)

    * fix(security): harden sanitizers and regexes flagged by CodeQL
    
    Address CodeQL code-scanning alerts across the sanitization and
    string-handling surfaces:
    
    - svgSanitize (upload boundary): close-tag regexes now match `</script foo>`
      / `</script\t\n>` / `</script/>` (bad-tag-filter), and stripping iterates to
      a fixpoint instead of a fixed 2 passes so split-tag obfuscation
      (`<scr<script>ipt>`) cannot survive. Adds the first test for this boundary.
    - sanitize.ts fallback: same close-tag fix + fixpoint loop in the
      no-DOMPurify-runtime path; plain-text post-strip reuses it.
    - markdownDocument.decodeEntities: decode `&amp;` last to stop the
      `&amp;lt;` -> `<` double-unescape (js/double-escaping).
    - siteItemNames: drop dynamic RegExp for startsWith/slice.
    - scaleModule / dependencyResolver: replace -> replaceAll.
    - htmlPagePlan importer: harden script close-tag match.
    - arch gate: cms-handlers-capability-gated skips __tests__ dirs.
    
    Verified: bun run build, bun run lint, bun test (5741 pass).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * fix(security): pair block+opener strips in sanitize.ts fallback
    
    CodeQL still flagged stripHtmlFallback (js/incomplete-multi-character-
    sanitization) because the generic `<[^>]*>` tag strip isn't credited with
    removing a residual `<script` / `<style` opener left by block removal.
    
    Mirror the proven server SVG sanitizer: each block regex is now paired with
    an explicit opener regex (`<script…>` / `<style…>`), making the dangerous
    substring provably gone — the same shape CodeQL already accepts in
    svgSanitize.ts.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * fix(security): extract stripHtmlOnce so CodeQL sees the fixpoint
    
    CodeQL's incomplete-multi-character-sanitization didn't credit the inline
    chained-replace loop body as a fixpoint, re-flagging the same lines. Mirror
    the exact structure CodeQL already accepts in svgSanitize.ts: extract the
    replace chain into stripHtmlOnce(), and loop `current = stripHtmlOnce(current)`
    to a fixpoint. Behavior unchanged.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * fix(security): per-regex do-while fixpoints in sanitize.ts fallback
    
    CodeQL evaluates each .replace() independently and only credits a replace
    that is itself looped to a fixpoint — an outer loop around a chain, or the
    chain in a helper, left every individual replace flagged. Restructure
    stripHtmlFallback into three single-literal-regex do-while-until-stable loops
    (script block, style block, remaining tags), the exact shape CodeQL's qhelp
    documents as the complete fix. Behavior unchanged: script/style content is
    still removed (covered by the no-DOM server-runtime test).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    DavidBabinec and claude authored Jun 29, 2026
    Configuration menu
    Copy the full SHA
    d32a262 View commit details
    Browse the repository at this point in the history
  3. feat(ai): MCP connectors — expose CMS tools to external AI clients (#109

    )
    
    * chore(ai): add MCP SDK + fetch-to-node, scope SDK isolation gate to drivers only
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * feat(ai): MCP connector model, token hashing, store + migration 018
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * refactor(content): extract actor-agnostic page-tree service; plugin handler delegates
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * feat(ai): page-tree MCP tools + capability-filtered MCP registry
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * feat(ai): MCP bearer auth, capability-scoped server, Web-standard HTTP transport mounted at /_instatic/mcp
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * feat(ai): MCP connector CRUD handlers (list/create/revoke) with privilege floor + audit
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * feat(admin): MCP connectors tab — create/list/revoke with capability picker + client snippets
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * docs(ai): document MCP connectors; scope SDK gate note; fix Button title gate
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * test(ai): deterministic end-to-end MCP flow (stateless initialize/list/call) over the real handler
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * test(ai): type the MCP e2e RPC responses (drop explicit any)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * fix(admin): MCP connector dialog reuses the Role dialog capability picker
    
    The capabilities list collided with the AiPage two-column field grid. Switch the
    dialog to the SiteCreateDialog form layout + the UsersPage capability-picker
    styles + shared CAPABILITY_META, grouped Read/Write, for visual consistency
    with the role management dialog.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * refactor(admin): extract shared CapabilityPicker used by Role + MCP dialogs
    
    Move the role dialog's capability checklist into a shared, presentational
    `@admin/shared/CapabilityPicker` (+ co-located CAPABILITY_META). RoleDialog and
    the MCP connector dialog both consume it, so they stay visually consistent and
    the markup/CSS lives in one place. UsersPage.module.css keeps only the role
    identity grid; users/utils/capabilities.ts keeps only the role grouping.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * feat(ai): MCP live editor bridge — expose the full tool catalog via an open editor
    
    Browser-execution editor tools (HTML/CSS authoring, design tokens, page
    lifecycle, content CRUD, code assets, live-DOM reads) have no server impl —
    they run in the editor app. This relays MCP calls for those tools to the
    connector owner's open editor and awaits the result, reusing the chat bridge
    machinery wholesale (createBridge / resolveBridgeToolResult / /tool-result).
    
    - server/ai/mcp/editorBridge.ts: per-user bridge registry + NDJSON stream.
    - GET /admin/api/ai/editor-bridge: the stream the editor holds open.
    - MCP registry now exposes the full deduped catalog; server.ts routes browser
      tools to the live bridge (clear 'open the editor' error when none connected).
    - src/admin/.../useEditorMcpBridge.ts: editor-side listener (mounted in SitePage)
      running executeAgentTool + postToolResult, with reconnect.
    - MCP picker expanded to all tool-backed editing capabilities.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * feat(ai): headless read_styles tool + clearer MCP results + write-flush + sharper descriptions
    
    Addresses live-use friction:
    - read_styles: returns the design system as a CSS stylesheet (tokens + classes)
      read straight from the DB via the publisher emitters — headless, no editor,
      no snapshot. Replaces the snapshot-dependent list_tokens (excluded from MCP
      along with list_breakpoints, which silently returned nothing over MCP).
    - MCP success with no payload now reads as {ok:true}, not the literal 'null',
      so mutating tools (deleteNode) report unambiguous success.
    - The editor bridge flushes the draft save after a write tool, so a follow-up
      headless read sees the change instead of stale state (fixes the duplicatePage
      'looked like it failed' race).
    - Sharpened read_page_tree/mutate_page_tree descriptions to steer the agent
      (headless-by-id vs editor HTML/CSS authoring; pointer to read_styles).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * fix(ai): forward MCP tool images (render_snapshot PNG was dropped)
    
    The MCP CallTool result emitted only text, discarding output.images — so
    render_snapshot's screenshot never reached the client. Forward images as MCP
    image content blocks. Adds an integration test relaying render_snapshot through
    the editor bridge and asserting the PNG arrives as an image block.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * fix(ai): delete headless read_page_tree/mutate_page_tree — one source of truth
    
    These MCP tools (added earlier this branch) edited the DB directly, creating a
    second copy of each page with identical node ids. Mixed with the open editor's
    browser tools they desynced, and the editor's autosave clobbered the headless
    write (data loss the agent reproduced). Superseded by the live editor bridge:
    ALL page editing now goes through the editor store (browser tools), persisted by
    the existing save-flush. Removed the two tools + their registry wiring; tests
    repointed to the headless content reads. treeService stays — the plugin RPC
    (cms.content.tree.*) still uses it.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * fix(ai): editor MCP bridge self-heals — never permanently stops on auth blip
    
    The reconnect loop set stopped=true on a 401/403, so a transient auth failure
    during a dev-server restart silently killed the bridge for the whole session
    ('editor open but MCP can't see it'). Now it always retries (longer backoff for
    auth failures) and logs on connect, so the bridge survives restarts and brief
    session blips.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * feat(ai): Tier 1 MCP tool improvements — list_breakpoints, read_styles summary, insertHtml created map
    
    - list_breakpoints: headless (reads the site shell) — fixes 'guessed desktop';
      replaces the snapshot-based version that returned nothing over MCP.
    - read_styles: format:'summary' returns a compact class catalog (selector +
      referenced token vars, no declarations) for cheap scanning before editing.
    - insertHtml now returns 'created' — every inserted node { id, moduleId,
      classes } — so the agent can target nested nodes without re-dumping the tree.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * feat(ai): Tier 3 — get_context tool (editor connected? which templates wrap pages?)
    
    Headless orientation tool: reports whether a live editor is connected (browser
    tools need it) and which everywhere/post-type templates wrap pages — the two
    things that silently confused live use. Reads bridge presence + template rows
    from the DB.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * refactor(ai): consistent snake_case + site_/content_ prefix for all agent/MCP tools
    
    Standardizes the whole agent toolset (shared by the built-in panel and MCP) on
    snake_case with a domain prefix, so the tool list self-groups and humans+agents
    can tell at a glance what's site vs content:
      insertHtml→site_insert_html, applyCss→site_apply_css, deleteNode→site_delete_node,
      addPage→site_add_page, render_snapshot→site_render_snapshot, read_styles→site_read_styles,
      create_document→content_create_document, list_collections→content_list_collections, …
    get_context stays unprefixed (cross-cutting). The old site/content list_documents
    name collision is resolved into distinct prefixed names.
    
    Scoped strictly to agent tool-name references (server defs, both executors, the
    panel row formatter, prompts, MCP registry/tools, SSOT gate, tests, docs) —
    never the identically-spelled TreeOperation kinds or editor-store methods.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * fix(ai): Tier 2 — scoped render_snapshot via iframe + importer no double-prop
    
    render_snapshot: the canvas renders each breakpoint inside an <iframe> (the
    data-breakpoint-id is on the host wrapper, the nodes live in the iframe's
    document). The capture searched the host frame, so scoped node lookups never
    matched and full captures grabbed the empty wrapper. Now it resolves the
    breakpoint's iframe and uses contentDocument as the node-lookup + screenshot
    root, and defaults to the active breakpoint (not the first DOM frame, which is
    mobile in a mobile-first layout). Scoped site_render_snapshot({nodeId}) and the
    right-viewport full capture now work.
    
    HTML importer: a node that recurses into child nodes no longer also keeps a
    flattened 'text' prop — children are the source of truth. Fixes a loop <a>
    wrapping spans/tokens that got BOTH a text prop and children (ambiguous,
    double-render risk). Mirrors the conditional heading/label rules.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * fix(ai): site_insert_html 'created' was always empty (stale pre-insert snapshot)
    
    runInsertHtml read activeDocumentNodes(store) from the snapshot captured BEFORE
    insertImportedNodes ran, so the just-created nodes (and auto-created classes)
    weren't in it — created came back []. Re-read fresh state after the insert.
    Now created returns the full inserted subtree { id, moduleId, classes } with
    resolved class names, so an agent can target nested nodes without re-reading the
    tree. Regression test added.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    
    * fix(ai): remove unused MCP review symbols
    
    ---------
    
    Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    DavidBabinec and claude authored Jun 29, 2026
    Configuration menu
    Copy the full SHA
    e554b0e View commit details
    Browse the repository at this point in the history
  4. Configuration menu
    Copy the full SHA
    ec7fc99 View commit details
    Browse the repository at this point in the history
  5. Configuration menu
    Copy the full SHA
    1a5625c View commit details
    Browse the repository at this point in the history
Loading