web: shellverbs.js — the behaviour seam (docs/shell-modules.md § The verbs seam) (#955)
The shellstate trick applied to behaviour: an empty registry that shell.js fills with one Object.assign at boot (hoisted function decls), so extracted modules call verbs.openThread(id) with no import cycle and no per-module hook soup. Registration only; no logic in the seam.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
3 files changed,
+34
−0
docs/shell-modules.md+15 −0web/static/shell.js+10 −0web/static/shellverbs.js+9 −0
diff --git a/docs/shell-modules.md b/docs/shell-modules.md| index 5d4b489..7293f01 100644 |
| --- a/docs/shell-modules.md |
| +++ b/docs/shell-modules.md |
| @@ -38,6 +38,21 @@ So the seam is `web/static/shellstate.js`: |
| - Nothing else lives in shellstate.js. It is a place for shared *state*, not |
| shared *behaviour*; helpers keep moving into their own modules. |
| +## The verbs seam |
| + |
| +The state seam unblocked modules that *read*; the remaining clusters also |
| +*act* — they open threads, reload the list, raise modals. Those verbs can't be |
| +imported from shell.js (it's the entry module; importing it back is a cycle), |
| +and injecting them per-module scales into hook soup. |
| + |
| +So the same trick again: `web/static/shellverbs.js` exports one empty |
| +registry object, and shell.js populates it in a single `Object.assign` at |
| +boot. Modules call `verbs.openThread(id)`. Registration only — no logic in |
| +the seam, nothing imported by it, no cycles. When a verb's implementation |
| +later moves into its own module, the registry entry moves with it and no |
| +caller churns. (searchui.js's `wireSearch` predates this seam and stays as |
| +is.) |
| + |
| ## Rules for a slice |
| 1. Verbatim move — refactor in a separate PR from relocation, never both. |
diff --git a/web/static/shell.js b/web/static/shell.js| index bcf2562..ba3e0e7 100644 |
| --- a/web/static/shell.js |
| +++ b/web/static/shell.js |
| @@ -133,6 +133,7 @@ import "/static/apps/admin.js"; // the admin panel (admin-only; opened via /admi |
| import "/static/apps/appstudio.js"; // make + manage your own member apps (docs/member-apps.md) |
| import { openMemoryPanel } from "/static/apps/memory.js"; // /memory — the AI-memory scratchpad (docs/eleven-memory.md) |
| import { setHost as setChatAppHost, setConnected as setChatAppConnected, appendNotice, setNoticeTyping, feedGlideToBottom, scrollToMessage } from "/static/apps/chat.js"; |
| +import { verbs } from "/static/shellverbs.js"; // the behaviour seam (docs/shell-modules.md) |
| import { scheduleMessageSearch, wireSearch, searchIdx } from "/static/searchui.js"; // message search over the local store (#586) |
| import { loadMemberApps, setMemberSeeder, setSurfaceRefresh, setMemberAdmin, recentLocalDeploy, pushTheme } from "/static/sandbox.js"; // member-built apps, run sandboxed (docs/member-apps.md) |
| import { THEME_META, THEME_ORDER, currentTheme, currentAppearance, setTheme, setAppearance, currentTextSize, setTextSize, onThemeApplied } from "/static/theme.js"; // applies the saved theme/text-size on import |
| @@ -176,6 +177,15 @@ import { |
| } from "/static/store.js"; |
| import { anyComposerHasText, formatAsYouTypePref, setFormatAsYouType, classicComposerPref, setClassicComposer } from "/static/composer.js"; |
| +// Populate the behaviour seam: the core verbs extracted modules call through |
| +// (function declarations hoist, so these references are valid here). |
| +Object.assign(verbs, { |
| + openThread, loadThreads, renderThreadList, renderActiveSurfaces, |
| + paintThreadHeader, modal, toast, displayName, threadAvatarFor, |
| + announceJoin, announceLeave, announceRemove, sendChatText, |
| + loadBlobBytes, chatAppFor, chatAppLoadOlder, stashAppPane, |
| +}); |
| + |
| onThemeApplied(pushTheme); // live-retint mounted sandbox frames on theme change |
| wireSearch({ openThread, chatAppFor, chatAppLoadOlder }); // navigation + thread predicate + scroll-back paging |
diff --git a/web/static/shellverbs.js b/web/static/shellverbs.js| new file mode 100644 |
| index 0000000..5ab4178 |
| --- /dev/null |
| +++ b/web/static/shellverbs.js |
| @@ -0,0 +1,9 @@ |
| +// The shell's behaviour seam (docs/shell-modules.md § The verbs seam) — the |
| +// shellstate.js idea applied to the core verbs. This registry starts empty; |
| +// shell.js populates it in one Object.assign at boot (function declarations |
| +// hoist, so the assignment can sit right after the imports). Extracted |
| +// modules call verbs.openThread(id) etc. Registration only — no logic lives |
| +// here, nothing is imported here, so there is never an import cycle. When a |
| +// verb's implementation later gets its own module home, the registry entry |
| +// follows it and callers don't churn. |
| +export const verbs = {}; |