Skip to content

Inline AI schema explanations in the editor - #165

Merged
josephschorr merged 4 commits into
mainfrom
feat/inline-schema-explanations
Sep 22, 2026
Merged

josephschorr merged 4 commits into
mainfrom
feat/inline-schema-explanations

Conversation

@josephschorr

Copy link
Copy Markdown
Member

Summary

Adds an agent-driven Explain control to the schema editor that overlays AI-generated explanations inline in Monaco — without ever modifying the schema text. A tristate toggle (Off / Compact / Full) controls density; a Regenerate button re-runs the agent on demand.

  • Off — clean schema.
  • Compact — a short ghost tag at the end of each symbol's line; click a tag to expand its full explanation block.
  • Full — a dimmed, indented explanation block above each definition / relation / permission / caveat.
  • Hover any symbol for its full explanation, with a regenerate link.

Explanations are generated by a new explain_schema assistant client tool (the model supplies the content; nothing is written into the schema). They anchor to symbols by name via the SpiceDB parser, so they re-position as you edit and mark themselves stale (dimmed + ↻) when a symbol's body changes — regeneration is on-demand (never per-keystroke) to control token spend.

Gated entirely on AppConfig().aiEnabled.

How it works

  • explain_schema client tool validates each symbol against the current parse, hashes its source for staleness, and writes a persisted Zustand store (useSchemaAnnotationStore).
  • A pure resolution layer (resolve.ts) maps symbol → Monaco range and computes staleness via @authzed/spicedb-parser-js (reads caveat ranges from the raw AST to work around a v1.2.0 wrapper bug).
  • renderAnnotations.ts paints after-decoration tags (Compact) and view-zone blocks (Full / click-expanded), width-constrained and indented to line up with the code.
  • Wired into EditorDisplay (schema editor only) with a once-registered dsl hover provider; the hover's command: trust is scoped to the regenerate command only, so LLM-authored text can't smuggle other executable command links into a trusted tooltip.
  • Backend: a short guidance paragraph added to PLAYGROUND_INSTRUCTIONS; no structural backend change — the tool executes client-side via the existing tool-handoff path.

Notable fixes made along the way

  • Inline annotations weren't painting until a tab-switch/reload — the schema editor uses automaticLayout: false, so decorations/view-zones added after generation weren't flushed; a forced editor.layout() on the next frame fixes it.
  • Guard against wiping prior annotations when a model call resolves zero symbols.
  • Reconcile the "generating" spinner with the assistant turn lifecycle (don't clear it before the turn starts; surface turn errors).

Testing

  • Unit: symbol resolution + staleness, the store, the explain_schema tool (incl. unknown-symbol handling), the generation trigger, light-markdown rendering, and the reconcileGeneratingStatus / shouldGenerate helpers.
  • Browser (Playwright): the Monaco renderer — Compact tags, Full blocks-only, Compact click-to-expand, and clearing on Off.
  • tsc --noEmit clean; oxlint clean aside from 2 pre-existing react/only-export-components warnings on co-located, test-imported helpers.

⚠️ The live LLM round-trip (toggle → OpenRouter → explain_schema → inline render) was verified manually with VITE_AI_ENABLED=true + an OpenRouter key. It is not covered by automated tests (there's no backend LLM mock), so give it a manual smoke test before merge.

@vercel

vercel Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
playground Ready Ready Preview Sep 19, 2026 4:20pm UTC

Request Review

Adds an agent-driven Explain control to the schema editor that overlays
AI-generated explanations inline in Monaco without modifying the schema text.
A tristate toggle (Off / Compact / Full) controls density; a Regenerate button
re-runs the agent on demand.

- Off: clean schema. Compact: a short ghost tag at each symbol's line end,
  click to expand its full block. Full: an indented explanation block above
  each definition/relation/permission/caveat. Hover any symbol for its full
  explanation with a regenerate link.
- New explain_schema assistant client tool (the model supplies the content;
  nothing is written into the schema). Explanations anchor to symbols by name
  via @authzed/spicedb-parser-js, re-position as you edit, and mark themselves
  stale when a symbol's body changes; regeneration is on-demand to control
  token spend.
- Pure resolution/staleness layer, a persisted Zustand store, a Monaco
  decoration + view-zone renderer, and a dsl hover provider whose command trust
  is scoped to the regenerate command only.
- Gated on AppConfig().aiEnabled; no structural backend change (client-side tool
  via the existing handoff path, plus guidance in PLAYGROUND_INSTRUCTIONS).

Covered by unit tests (resolution, store, tool, trigger, helpers) and a
Playwright renderer test. The live LLM round-trip is verified manually only.
samkim and others added 2 commits September 18, 2026 16:04
Schema, Assertions, Expected and Relationships (code editor) render
EditorDisplay at the same tree position, so React reused one instance and
only swapped the Monaco model. EditorDisplay registers its editor under the
item it was first mounted for, so when that wasn't Schema the annotation
renderer found no editor for Schema and silently skipped rendering.

Key each EditorDisplay by its document so a tab switch remounts it, as the
grid path already did and as the pre-editor-groups layout did.

Adds browser regression tests covering Full and Compact modes across tab
round trips, including the case where the shared editor is first mounted
for another tab.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lanations

The explain_schema tool wrote its results with `input.show ?? "full"`, so
turning on Compact and generating jumped the toggle to Full as soon as the
explanations arrived. The model is never told about `show`, so the default
always won.

When explanations are already showing, keep the user's density; `show` (default
Full) now only decides how to reveal them when they were hidden. Document that
on the `show` parameter and add tests for each case.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Formatting only: re-wrap a few long statements and order imports, so that
`npm run format:check` passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@josephschorr
josephschorr merged commit a878a20 into main Sep 22, 2026
5 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 22, 2026

This branch was successfully deployed

1 active deployment
Preview — 5041a906 Deployed Sep 19, 2026 by vercel[bot]
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants