Skip to content

sdk: a plugin can put a tray's or a lone float's view away - #593

Merged
foxnne merged 3 commits into
mainfrom
sdk/hide-surface-narrow
Oct 18, 2026
Merged

foxnne merged 3 commits into
mainfrom
sdk/hide-surface-narrow

Conversation

@foxnne

@foxnne foxnne commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Part of fizzyedit/work#33

Supersedes #566, narrowed to trays and lone floats. #566 grew into reworking the explorer's state, Split.close and a mid-frame reveal, and kept regressing; this keeps to the one primitive and leaves everything around it as it is on main.

What changes

A plugin can put its view away, not only show it, so a button can toggle it:

pub fn hideSurface(self: *Host, id: []const u8) bool

if (!host.hideSurface(id)) _ = host.revealSurface(id); // a toggle

It acts in two cases only, each the way the user's own gesture does:

  • The view is what a tray shows. A tray is a place the shape declared resize and not forget_when_empty (so not a place a split made), in the main window. If it is open, not folded, and id is its shown view, it shuts with whatever else it holds, through Region.close, as its rail icon or close does. revealSurface opens it again at the size the user left it.
  • The view is alone in a float. The float closes through Layout.closeFloat, as its close button does, and the view goes home.

Anything else returns false and changes nothing: Main, a split's half (of a tray too), a plugin's pane, a view behind another tab, a view out of sight, a float with other views.

How:

  • Layout.hideTarget(id) is the one predicate. It finds the region showing id the way reveal does, and says what a hide would do (Hide.tray / Hide.float) or null. Layout.hide applies it at once (between frames).
  • During a frame (a button in a view's draw), the shape and the floats are walking the lists a hide changes. Layout.hideLater answers with the same hideTarget and queues the id; Layout.applyHides does the queued hides once the shape, floats, drag overlay and picker have drawn, each asking hideTarget again. revealSurface is unchanged (immediate), so a hide asked first in a frame lands after a reveal asked later in it: a toggling caller makes one call a frame. Documented.
  • The registry entry of a declared place now carries its resize (one line in Region.init's registration), which is how hideTarget knows a tray.
  • Editor wires the vtable member. A hide that shut the sidebar then calls the explorer's existing Explorer.close, so its closed flag (Toggle Explorer, its menu title) agrees; nothing in Explorer changes.
  • A test fix this needs. markdown preview: skipping off-screen blocks lays the document out identically lays out docs/PLUGINS.md itself and compared two f32 totals near 41000 pt to an absolute 0.01, where one rounding step is 0.004. The section added here tipped it (41430.58 vs 41430.59, locally and on CI's Linux run); it now compares relatively, at 1e-6.
  • The extents_changed check in fizzy's frame moved below the picker and the applied hides, so a change either makes is saved too.

Contract: docs/PLUGINS.md §3.1.2, "Showing and putting away a view".

SDK impact

  • None
  • Core-only or additive: reaches plugins at the next SDK release
  • Fingerprint moved: recorded in sdk/src/version.zig (0x1dd3a67d781503a7, combined with main's after sdk: a plugin's data folder entry goes with the plugin #587), sdk_version untouched, PR labelled sdk. New: EditorAPI.VTable.hideSurface, EditorAPI.hideSurface, Host.hideSurface.
  • SDK release: bumps sdk_version, lists the sdk PRs since the last sdk-v* tag

Verified

  • macOS, on the merge with main: all gates pass (scripts/check-fmt.sh, zig build, test, test-integration, check-web, test-sdk-version, check-tests-reached, scripts/check-examples.sh). New integration tests on the ManyPanelCase harness (whose frame now applies hides at its end, as fizzy's does):
    • a tray's view shuts the tray; a second hide and an unknown id are false; reveal reopens it; hide again shuts it (the toggle);
    • a view in Main, and in a place split off Main, is false and nothing changes;
    • a view in a tray the user split is false and nothing changes;
    • a view alone in a float closes the float and goes home to the panel;
    • a view in a float with another is false for both views, and the float stays;
    • a floating view asking from its own draw: the answer is true, the float is still open while it draws, and it is closing by the frame's end, then gone.
  • Windows:
  • Linux:
  • Web: builds (check-web)

Follow-ups

Deliberately left out, each a case hideSurface returns false for today:

  • views in a split tree of a tray (a panel the user split): which half shuts, and whether the tray does;
  • views in a float with other views (send one home, keep the float);
  • folded trays (a sidebar folded away on a narrow window), which count as out of sight;
  • revealSurface reopening the sidebar leaves Explorer.closed as it was (as on main); worth one source of truth for the explorer's state in its own PR.

🤖 Generated with Claude Code

`Host.hideSurface(id)` is `revealSurface`'s other half, so a button can
toggle its view. It acts in two cases only: the view is what an open tray
(a resizable place the shape declared) shows, and the tray shuts as its
own close does; or the view is alone in a float, and the float closes as
its close button does. Anything else returns false and changes nothing.
Asked during a frame it answers at once and acts once the frame has drawn
(`Layout.hideLater` / `applyHides`). Supersedes #566, narrowed.

Part of #530

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne foxnne added the sdk Moves the plugin boundary's fingerprint; ships at the next SDK release label Oct 18, 2026
@foxnne
foxnne marked this pull request as ready for review October 18, 2026 21:48
foxnne and others added 2 commits October 18, 2026 17:19
The SDK fingerprint is the combined value, 0x1dd3a67d781503a7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The markdown preview test lays out docs/PLUGINS.md and compares the sum
of its block heights with the scroll area's total to 0.01 pt. Both are
f32 sums near 41000 pt, where one rounding step is 0.004, so the
contract section this PR adds to PLUGINS.md tipped it over (41430.58 vs
41430.59, on macOS and on CI's Linux run). Compared relatively, at 1e-6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@foxnne
foxnne merged commit 7a873d4 into main Oct 18, 2026
11 checks passed
@foxnne
foxnne deleted the sdk/hide-surface-narrow branch October 18, 2026 22:42
@foxnne foxnne mentioned this pull request Oct 19, 2026
2 of 8 tasks
foxnne added a commit that referenced this pull request Oct 19, 2026
## What changes

`sdk_version` goes from 0.2.30 to 0.2.31. Merging this tags
`sdk-v0.2.31` and publishes its tarball. `sdk-repin` then asks each
store plugin to repin.

**Why now:** the SDK's shape has moved several times since 0.2.30. Store
plugins and apps built on fizzy, Graphl among them, only get the new
calls once they're released. Most of them come from Graphl's report.

## SDK impact

- [ ] None
- [ ] Core-only or additive: reaches plugins at the next SDK release
- [ ] Fingerprint moved: recorded in `sdk/src/version.zig`,
`sdk_version` untouched, PR labelled `sdk`
- [x] SDK release: bumps `sdk_version`, lists the `sdk` PRs since the
last `sdk-v*` tag

**`sdk` PRs since `sdk-v0.2.30`:**
- **#560: a plugin's own data folder.** `host.pluginDataFolder(plugin)`
gives `<config>/plugin-data/<id>`, made on first ask. It's the plugin's
own from `register` through `deinit`, and stays on disk after uninstall.
- **#587: the data folder goes with the plugin.** `Host.retirePlugin`
unregisters a plugin, runs its `deinit`, then tells the app, so a plugin
can still write out state in `deinit`. On `EditorAPI`,
`pluginUnregistered` became `pluginRetired`; that vtable is implemented
by the app, not by plugins.
- **#593: put a view away.** `host.hideSurface(id)` shuts the tray whose
shown view is `id`, or closes a float that holds only `id`. Anything
else returns false. Mid-frame it acts after the frame has drawn. A
toggle is `if (!host.hideSurface(id)) _ = host.revealSurface(id);`.
- **#603: times on recent folders.** `host.recentFolderOpened(i)`
returns when recent folder `i` was opened, in Unix seconds, or null if
unknown. `recentFolderAt` is unchanged.
- **#599: a home page.** `sdk.keywords.ide.home` is new. While the
workbench's pane is empty, it shows a surface keyworded `home` in place
of its own page. The fingerprint didn't move; a plugin on an older SDK
can write `&.{"home"}`.
- **#581: undo is announced.** Undoing someone else's step is announced
as `fizzy.history.undone`, with a payload type `events.StepTaken`.
Additive; the fingerprint didn't move.
- **#609: history restore.** An optional `restoreHistory` hook and
`sdk.Plugin.RestoredHistory`. Plugins without the hook are unaffected.

**Core change plugins pick up at this release** (no boundary change):
#550, a replayed backdrop captures inside a front-to-back block. (`app/`
changes since 0.2.30, such as #570 and #602, are not compiled into
plugins.)

## Verified

- [x] macOS: `scripts/check-fmt.sh` and `zig build test-sdk-version`
pass. The fingerprint is main's recorded value; only `sdk_version`
changed.
- [ ] Windows:
- [ ] Linux: CI
- [ ] Web:

## Follow-ups

- Repin and release the store plugins (pixi, zig, ghostty, drive, atlas;
then agent and chat) when `sdk-repin` opens their PRs.
- Make 0.2.31 live on fizzyed.it/app once the repinned plugins are in
the store: `gh workflow run web.yml --ref main -f go_live=0.2.31`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sdk Moves the plugin boundary's fingerprint; ships at the next SDK release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant