feat(widgets): standalone .widget files — own leaf, embeds, agent-writable, link previews - #506
Merged
Merged
Conversation
Contributor
|
A new workspace view type shows one `s2b-view` fence of a note filling the pane, with no note chrome. The note stays the source of truth: the leaf re-renders when the note changes on disk (which is how an agent edit accepted through the review flow lands), follows renames, and persists its target (note path + fence index) with the workspace layout. Header actions open the source note and refresh. Entry points: a hover-revealed corner button on any rendered view in a note, a third toolbar button in chat that saves the block (once) and opens it, and an "Open note as view" command on the active note. The render child gains a `fill` mode — the frame takes its container's height and ignores auto-sizing — and `viewFences.ts` locates a note's top-level fences (nested example fences excluded) so a block can find its own index from the section info the processor receives. Co-Authored-By: Claude <noreply@anthropic.com>
…saves once Rename: the renderer resolves queries and note links against its source path, so the leaf now re-renders after a rename instead of only updating its state. Fence identity: the persisted ordinal alone let an edit that inserted or removed an earlier fence silently redirect the leaf. The fence's title is stored beside the ordinal; a fence with that title wins, the ordinal is the fallback for untitled fences, and a target that no longer exists (including a retitled one, indistinguishable from a redirect) shows a missing-view state rather than another view. Reopening from the note re-targets. Opening an already-showing leaf re-applies the fresh target. Chat toolbar: both save buttons share one in-flight save promise, so two quick activations cannot create two notes; a failed save is forgotten so a retry is possible. Co-Authored-By: Claude <noreply@anthropic.com>
Leo310
force-pushed
the
feat/gen-view-leaf
branch
from
September 17, 2026 12:49
c57f0a3 to
6a6dbe6
Compare
The frame width leaves room for its border; in a leaf the border is 0, so the variable it reads is 0 there too and no 2px gap remains. Co-Authored-By: Claude <noreply@anthropic.com>
The header was built before the state arrived and the not-found path never refreshed it, so the tab read "View" instead of the note's name. Co-Authored-By: Claude <noreply@anthropic.com>
… out of the index Views become files, not fences in notes, when kept on their own. A `.view` file holds exactly a fence's body (optional frontmatter, then HTML) and is bound to a FileView that fills the pane; it re-renders when the file changes on disk, follows renames and deletes like any file view, and — since Obsidian has no editor for the extension — carries a minimal source mode of its own (textarea, save/cancel, Cmd/Ctrl-S) behind a header action. `![[Name.view]]` embeds and hover previews render through the embed registry the `.chat` previews already use. The chat toolbar's "save as note" and "open as view" collapse into one: save as a `.view` file, which opens it. The corner button on fences in notes and the note-index targeting (ordinal + title) go away with the note-backed leaf they served. Why a file: a fence in a note put its HTML and JavaScript into Obsidian's search and, worse, the plugin's retrieval index; a `.view` file is indexed by neither. Fences in notes stay for inline use, and their bodies are now replaced by a `(view: <title>)` marker before indexing so a note that holds a dashboard is still findable by name without its code becoming chunks. `manage_notes` accepts `.view` paths so the agent creates and revises views through the same staged review as notes. Co-Authored-By: Claude <noreply@anthropic.com>
…rop stale loads The source editor remembers the file's mtime when it loaded the text. A change that lands meanwhile (an accepted agent edit, sync) is announced in the editor's bar and makes Save refuse rather than write the older copy over it; Cancel reloads. The embed renderer tracks a generation per load and on unload, so an older read that finishes late cannot replace newer content or install a child into an unloaded embed. Co-Authored-By: Claude <noreply@anthropic.com>
Any element with data-note="<path>" is a note link: the frame runtime opens it on click and reports its box on hover; the host lays an invisible proxy over that box (the frame's content is unreachable from the host) and hands it to Obsidian's page preview as the link, under a registered hover source. The proxy takes pointer events so the popover's own hover tracking works natively, opens the note on click (modifier-aware), and removes itself when the pointer leaves it. The skill's example and API notes use data-note. Co-Authored-By: Claude <noreply@anthropic.com>
…ke one in a note The runtime forwards the click's modifier keys with open-note and the host maps them through Keymap.isModEvent, so Cmd/Ctrl-click opens a new tab and the alt/shift variants a split or window — regardless of whether the hover proxy was in place for that click. Co-Authored-By: Claude <noreply@anthropic.com>
… after hover; parse test The note-link regex in the runtime template literal used an escaped slash pair; the formatter collapsed the escapes and the literal turned them into a comment marker, ending the regex early. The runtime then failed to parse in every frame — no s2b global, no ready, no resize, no hover — which is what made link previews never trigger. The regex now uses a character class with no backslashes, and a unit test parses both the inner runtime and the outer relay script as JavaScript so a break like this fails the suite instead of the frame. Page preview can be set to require Cmd/Ctrl per source, and for a link in a note it reacts to the key being pressed while already hovering. The proxy now does the same: a modifier keydown while it is up re-triggers the preview with the current modifier state. Also treats an anchor whose href is a vault path (not a URL, fragment or scheme) as a note link, the shape a model writes unprompted, alongside data-note. Verified with a trusted pointer and key events driven through the debugger protocol: hovering a link inside a frame with Cmd held, or pressing Cmd while hovering, opens Obsidian's page-preview popover. Co-Authored-By: Claude <noreply@anthropic.com>
decodeURIComponent threw on raw vault paths such as "50% off.md"; decoding is now attempted and the raw href used when it is not valid encoding. Co-Authored-By: Claude <noreply@anthropic.com>
"View" collided with Obsidian's own vocabulary — reading view, workspace views, and Bases, where every file has "views" in exactly the user-facing sense — and said nothing about what the thing is. "Widget" describes it: a self-contained interactive UI element that renders in place, embeds in notes, and can stand alone in a pane. Nothing had shipped, so the rename is total: the `s2b-widget` fence, the `.widget` file and its view type, the `widgets` core skill, the "Widgets folder" setting, the page-preview hover source, the CSS classes, the bridge tag, the code module and the tests. Obsidian's own "view" terms (FileView, reading view, view types) are untouched. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
The rename's word pass turned s2b-widget-view into s2b-widget-widget in the widget view's element classes while the stylesheet kept s2b-widget-view, so the pane body and source editor lost their layout rules. Co-Authored-By: Claude <noreply@anthropic.com>
A third toolbar button, "Expand", opens the widget in a modal sized to almost the whole window for a closer look without saving it. It renders a fresh copy in fill mode — an iframe cannot be moved in the DOM without reloading — and tears it down on close; Esc and the close button work as in any modal. Exposed as `plugin.openWidgetModal(spec, sourcePath)` so other entry points can reuse it. Co-Authored-By: Claude <noreply@anthropic.com>
…how to revise one read_content only reads extensions on the shared text allow-list, so the agent could edit a saved widget through manage_notes but not see its current source first. `.widget` is on the list now, and the widgets skill spells out the revise flow: read, stage a find/replace or rewrite, the user reviews the diff, and the open pane and embeds re-render on accept. Widget files are deliberately absent from search, so it also says to ask for the path or list the widgets folder. Co-Authored-By: Claude <noreply@anthropic.com>
Widget files were kept out of the indexes entirely so their markup and script could not become retrieval chunks — which also meant the agent could not find one by name. They are indexable now, but contribute only their frontmatter `title` and the new optional `description` line; the body never enters the index. The skill asks for a description on every widget and points search_notes at it for revisions. Co-Authored-By: Claude <noreply@anthropic.com>
layout-dashboard is the Canvas icon; widgets (the file view's tab and the core skill) now use app-window, verified to resolve in the bundled icon set. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
`icon:` in the frontmatter names a Lucide icon shown on the widget's tab and in the chat toolbar; the default stays `component`, and a name Obsidian's icon set does not know falls back to it rather than leaving a blank tab. The skill suggests fitting icons (chart-column, calendar, list-todo, gauge…). Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
The skill's icon examples read as an allow-list, and a home dashboard came out with a gauge. It now says any Lucide name works and that the icon should match the widget's subject, with examples framed that way. Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Sep 17, 2026
Leo310
added a commit
that referenced
this pull request
Sep 18, 2026
Widgets: the agent renders charts, tables and dashboards in the chat as sandboxed frames fed by live Dataview queries, with Plotly for 2D/3D plots (#505); standalone .widget files with their own pane, embeds and link previews (#506); widgets shown in the chat first, Rename and Edit source in the tab menu (#507); agent edits to a widget reviewed as the rendered result (#508). Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The "custom view" half of the widgets feature (renamed from "view" in this PR: it collided with reading view, workspace views and Bases' views, and said nothing about the thing itself). A widget kept on its own is a
.widgetfile: exactly what as2b-widgetfence body holds (optional frontmatter, then HTML), bound to aFileViewthat fills the pane. It re-renders when the file changes on disk (so an agent edit accepted through the review flow lands there), follows renames and deletes like any file view, and — since Obsidian has no editor for the extension — carries a minimal source mode of its own (textarea, save/cancel, Cmd/Ctrl-S; refuses to save over a file that changed meanwhile) behind a header action.![[Name.widget]]embeds and hover previews render through the embed registry the.chatpreviews already use.The chat toolbar's "save as note" and "open as view" collapse into one button: save as a
.widgetfile, which opens it. Copy-as-block stays for inline use.Note links inside widgets preview like links in a note. Any element with
data-note="Path.md"(or an<a href>holding a vault path) is a note link: click opens it (modifier-aware), hover reports its box, and the host lays an invisible proxy over that box and hands it to page preview under an "S2B Widgets" hover source. Pressing Cmd/Ctrl while already hovering re-triggers, matching Obsidian's behaviour for links in notes.Why a file: a fence in a note put its HTML/JS into Obsidian's search and into the plugin's retrieval index. A
.widgetfile is indexed by neither, and fences that do live in notes are replaced by a(widget: <title>)marker before indexing, so a note holding a dashboard stays findable by name without its code becoming chunks.manage_notesaccepts.widgetpaths so the agent creates and revises widgets through the same staged review as notes; thewidgetsskill (renamed fromviews, unreleased) documents the file form and the link attribute.Also fixed on the way: the frame runtime had been broken by a formatter-rewritten escape (a unit test now parses both frame scripts as JavaScript), and the render child gains a
fillmode.Caveats: Obsidian Sync only syncs unknown file types if "sync all other types" is enabled; links written inside a
.widgetfile are not parsed by the metadata cache (links to the file work).How I tested it
bun run check,format,lint,testpass. Live in theS2B WT2slot vault on macOS desktop: as2b-widgetfence in a note renders;![[X.widget]]embeds render at content height; opening a.widgetfile gives the leaf with the frame filling the pane and the edit/refresh actions, and the source editor round-trips. Link previews verified with trusted pointer and key events driven through the debugger protocol: hovering a link inside a frame with Cmd held opens Obsidian's popover, and pressing Cmd while already hovering opens it too. Not yet exercised live: the chat-toolbar save button end to end, an agent creating a.widgetthroughmanage_notesand its review, workspace restore after restart, and mobile.AI assistance: Claude Code (Fable 5.1) designed and wrote the change from Leo's brief, the file-first decision and the rename he made in discussion; Leo has tested intermediate builds live and reported the issues fixed here.
Checklist
bun run check,bun run format,bun run lint, andbun run testpass locallymanifest.json: I noted that the docs site needs updating (see "Documentation" in CONTRIBUTING.md) — thewidgetsskill and the.widgetfile type need documenting on the site; feat(views): render agent-generated views as sandboxed frames #505's mergedviewsnaming (fences2b-view,viewsskill,viewsFolder) is superseded by this PR.