Repository navigation
feat(gui-extensions): add typed composition and lifetime primitives - #52868
Merged
Merged
Conversation
…a pure main host core
Phase 1 of the extension SDK overhaul. Built-ins are unchanged; the old shapes stay until they migrate.
- Renderer: Live<T> for declared dependencies, createActive (with otherwise), createLatest, createVisitState, declared Store.app/Store.session with app stores preloaded before setup and queued writes, SessionView.visit, registrations that follow the current Solid owner and dispose when late, layout writes held until the session is located, requires gating, and an error boundary per contribution.
- Typing: Extension.define with provides/uses/requires/stores, Setup<typeof Def> exposing only declared dependencies, and Extension.compose failing to compile on a missing or duplicate provider (RemotesProvided checks renderer remotes against the main composition).
- Main: a lifecycle core without Electron (queue, generations, withdrawal, cleanup deadline, kept-scope handoff, last good revision on a failed reload, stall log) behind a thin Electron adapter, plus Scope with Effect's names and MainApp.restart(handoff, { keep }).
- Tests: table tests per primitive, type-level composition tests, the extension graph test, lifecycle tables and a seeded lifecycle fuzz test.
- Lint: bun run lint:changed fails when a touched GUI package file has any oxlint problem, including the warn-level anti-slop rules; CI runs it on pull requests.
…and lifetime primitives Phase 2 of the extension SDK overhaul. - Every built-in declares provides / uses / requires / stores and reads dependencies as Live values; actions that need an optional dependency answer while it is inactive. - About 88 raw effects replaced: createActive for side work per provider generation or identity, memos for derived values, createLatest for async data, createVisitState for per-visit state, handlers for user intent. - Review and summary state live in declared stores (same keys and legacy imports); session-store writes made before load are queued; session stores keep their own session identity. - Layout.sidebar.opened() replaces review's mirrored sidebar preference; Remote.ref declares a remote by id with type-only imports, so the browser's pane remote adds no schemas to startup. - src/renderer.ts and src/main.ts export Extension.compose; builtins.typecheck.ts fails to compile when a renderer remote has no main provider; the graph test covers every declared optional edge. - Deprecated shapes and Live.legacy are removed, and oxlint bans createEffect, createRenderEffect and createComputed in extension code outside src/sdk. - Solid computations created without an owner in tab-popover and the composer editor are created under their component; the btw keeper fails on the warning.
Remote becomes Ipc (IpcProvider for what main holds), Service becomes Contract, Host becomes HostApi, Native becomes Desktop, Surfaces becomes Embeds, the Link, Menu, Menubar, Status and Setting points become LinkHandler, MenuItem, MenubarItem, TitlebarItem and SettingsPage, createActive becomes createKeyed, SessionView becomes MountedSession, MainStorage becomes Storage, MainServer becomes ServerEndpoint and Store.app becomes Store.global. The renderer App splits into Build, Locale, Appearance, Router, Keybinds, Servers and Workspaces; main's MainApp into Build, Servers, Lifecycle and Log. Host files and identifiers follow the SDK names, and app code calls the side and dock open state a region. Stored keys, DOM ids and copy are unchanged.
…al shapes
Host APIs are lazy properties on the setup context and useExtension() (ctx.layout, ctx.storage, ctx.build, ...); ctx.use is removed and declared dependencies are read only through ctx.uses and ctx.requires, with ctx.uses.pane.load for Ipc.ref. Layout.open takes { tab, background }; PanelTab uses pinned, draggable and closable flags. Build has one shape in both processes and main's endpoints API is ServerEndpoints. Each process has one teardown and one signal (onCleanup and ctx.signal in the window, ctx.scope in main). Main storage returns Persisted. System.open becomes System.openExternal and the shell.bottom slot becomes window.bottom. IPC channels, payload fields and the browser embed event use the SDK names. Stored keys, DOM ids and copy are unchanged.
…to their owners - The summary extension is now details (command details.toggle) and usage is now context (panel key context:main). Stored state, keybinds and panel keys migrate from every older id, and a migration test table proves each mapping. - The Preferences host API is removed. The updater owns releaseNotes and What's New (seen versions migrate from highlights.v1); review owns mobile diff wrap through a SettingsPage section in the general page. Both import once from settings.v3 and render in the same place with the same copy. - PanelTab.transient names the launcher behaviour; closable only chooses the close button. - Persisted.update applies a returned value in both processes, and main stores a decoded copy. - storage.remove no longer lets a store re-import from its from source. - Store from accepts a list of older keys. - windows-menu.tsx creates its computations under its component owner.
Pins when the dialog shows, paging, media, Get started, Don't show these in the future, the failed-request retry, and the upgrade rows for highlights.v1 and settings.v3 releaseNotes.
Every exported SDK declaration, member, option field and parameter has TSDoc, and script/sdk-docs.ts (run by bun run lint) fails on a missing one, a stale @PARAM, an unknown ctx member in the guide, or a guide code block that differs from src/example. README.md is the author guide; src/example is a small two-process extension that CI typechecks and tests and that never ships. AGENTS.md is rewritten for the final API.
The extension graph matrix, the real-host extension tests, the example extension and the browser pane tests live in packages/app/component-tests and ran only locally.
- Provides are readable through ctx.uses without a second declaration; conflicting keys are compile errors. - MountedSession retains one session's identity and data. Screen-wide files, comments and composer live under ctx.screen.current(), which explicitly follows the route. - Reactive panel and slot inputs receive new sessions without extra remounts. Session stores no longer need to pin route-following identities. - MenuItem is discriminated by menu. Dialogs.open returns its own close handle. Host APIs return defaults and queue writes before the interface mounts. - Store.main declares main-process stores alongside window stores, with typed legacy sources and process-specific ctx.stores. - Remove the unused app slot, honor every file-search option, and allow informational titlebar items without actions. - Keep the browser picker on the routed session and abandon pending btw questions when leaving it. - Update the author guide, example, TSDoc and contract tests together.
Keep upstream Git initialization, browser geometry transition tracking, unused-code cleanup and version updates while preserving typed composition, context properties and screen-owned extension models.
Remove the non-shipped example extension and its test. The guide walks through pairing and other built-ins, with code blocks checked against their source declarations.
Clear the file store before effects can load files from a new directory, so a file tab opened during a cross-folder session switch keeps its content. Reset keybinds by deleting the key, so migrated shortcuts leave no stale row.
A dialog opened during startup now waits for the app interface, like other early host writes. This keeps What's New above a restored Settings screen and keeps keyboard focus inside it. Closing a waiting dialog cancels it.
Release SSH resources as each is acquired, follow re-authenticated server data in the browser model, share one main storage cache per key, clear stale createLatest errors, restrict main provide to declared Ipcs, add createKeyed equals for object sources, and derive the review mode instead of overwriting the stored choice.
Give a moved session a new MountedSession for its new directory, cancel dialogs closed before their deferred open, bind session stores to the live session ref, keep older store homes readable when a pick finds nothing, and import main storage from the first source the schema accepts.
…sion stores Attach host APIs only once every extension has settled and the routes render, so early writes and dialogs land after the interface's first render. Keep the routed session's stores and stores no tab has listed yet when pruning closed tabs. Report ctx.screen as unavailable in extension stories.
The pull_request event's base SHA can predate the base branch commits GitHub merges into the checked-out ref, so lint:changed counted upstream edits as the pull request's. Diff against the merge commit's first parent instead.
This was referenced Oct 4, 2026
Ichinose-Kazuki
pushed a commit
to Ichinose-Kazuki/opencode
that referenced
this pull request
Oct 7, 2026
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.
Typed composition, without an Effect runtime
Scope. Registrations withdraw with their owner.Author API
App/MainApptokensctx.build,ctx.layout,ctx.locale,ctx.serverEndpoints, …ctx.useswithLive: pending, active, inactivecreateKeyed,createLatest,createVisitStateRemote,Service,Native,SurfaceIpc,Contract,Desktop,EmbedMountedSession; route-following actions live onctx.screen.current()onCleanup; mainctx.scope.addFinalizerMenuItemis discriminated by menu; dialogs return their own close handles.PanelTabflags describe independent behaviours;Layout.opentakes{ tab, background }.summary→detailsandusage→context, with storage, keybind and panel-key migrations. Settings and What's New move to their owning extensions; existing copy stays unchanged.Enforcement and guide
sdk-docsrejects undocumented exports, members and parameters, and guide examples that drift from their source.Behaviour differences
data: null.No intentional layout or copy changes.
Performance
Production builds,
v2d113a40f55avs this PR. All runs pinned to P-cores.