Repository navigation
Frontend: move the start sequence into an exported bootstrap() for embedding apps #882
Copy link
Copy link
Closed
Copy link
Labels
enhancementNew feature or requestNew feature or requestfrontendFrontend (React/TypeScript)Frontend (React/TypeScript)
Milestone
Description
Goal
The frontend start sequence lives in one exported function bootstrap(). An embedding app with its own frontend entry point calls it instead of copying main.tsx, so every future start step (platform attribute, context-menu suppression, startup state, language, startup gate, root error boundary) reaches it automatically.
Context
apps/smart-ssh-community/frontend/src/main.tsxholds the whole start sequence as top-level code::13document.documentElement.dataset.platform = detectFallbackPlatform()(Spec 0114, narrow Android layout):33-38getStartupState()before the first render (Spec 0101, A16):48-50initI18n(...)with the language from the startup state:53installContextMenuSuppression()in production builds (Suppress the WebView context menu ("Reload") in release builds; add "Refresh" to the file browser's empty-space menu #629):56-62createRoot(...).render(<StrictMode><StartupGate initialState=…><App/></StartupGate></StrictMode>)- after Frontend: isolate every extension contribution and the app root with an error boundary #880: the root
ErrorBoundarywith the recovery screen.
- An app that embeds this frontend and registers its own extension contributions has to repeat these steps; steps added later (like
:13and:53) are easy to miss there. - Extensions that need React lifecycle (react to a state change, register and unregister a contribution) today have to wrap
<App/>in their own components, one nesting level each.
Requirements
- New
src/bootstrap.tsxexporting:It performs exactly the steps of today'sexport interface BootstrapOptions { /** Runs after initI18n and before the first render; may be async. */ beforeRender?: () => void | Promise<void>; /** Headless components rendered once as siblings of <App/> inside the * StartupGate (so only after unlock), each in its own ErrorBoundary. */ extensions?: ReadonlyArray<{ id: string; Component: ComponentType }>; /** Optional wrapper around <App/> inside the StartupGate. */ wrap?: (app: ReactNode) => ReactNode; } export async function bootstrap(options?: BootstrapOptions): Promise<void>;
main.tsx, in the same order, thenbeforeRender, then renders. The root element id staysroot. main.tsximports./index.cssand callsawait bootstrap()– nothing else.- A failing
beforeRender(throws/rejects) is logged withconsole.errorand the app still renders (fail-open only for this optional hook; startup state and gate behaviour unchanged). - Each entry of
extensionsis wrapped inErrorBoundary(from Frontend: isolate every extension contribution and the app root with an error boundary #880) keyed by itsid; a throwing extension does not affect<App/>. - Spec 0038 section 4 (frontend extension points) describes
bootstrap()and its options as the supported entry point for embedding apps.
Acceptance Criteria
-
bootstrap()exists with the options above;main.tsxcontains only the CSS import and thebootstrap()call. - Tests (vitest, with
getStartupState/initI18nmocked):-
bootstrap()setsdocument.documentElement.dataset.platform. - In a production build flag (
import.meta.env.PRODstubbed true) it installs the context-menu suppression; withPRODfalse it does not. - Order:
getStartupState→initI18n(with the startup-state language when locked) →beforeRender→ render. - A rejecting
beforeRenderis logged and the app still renders. - An
extensionsentry renders inside the gate only in the unlocked state; a throwing entry leaves<App/>rendered. -
wrapreceives the<App/>element and its result is rendered inside the gate.
-
- Existing startup tests (password mode, locked start, language before first render) and the Playwright suite pass unchanged.
- Spec 0038 section 4 updated.
- Relevant automated tests pass (
npm run lint,npm test,tsc -b,npm run test:e2e).
Technical Notes
- Files:
src/bootstrap.tsx(new),src/main.tsx, a newsrc/bootstrap.test.tsx,docs/specs/0038-repository-app-shell-ci.md. - Keep the long comments from
main.tsx(why the startup state comes first, Spec 0101 A16; why the language is set before the first render, Spec 0024) next to the code they explain inbootstrap.tsx; new comments in English. - Top-level
awaitmoves into the async function;main.tsxkeeps one top-levelawait bootstrap(). StartupGatekeeps sole control over when<App/>mounts;extensionsare rendered inside it so they never call data commands while the app is locked (Spec 0101 A16).
Spec impact: update 0038
Open questions
- none
Related Work
- Frontend: isolate every extension contribution and the app root with an error boundary #880 (ErrorBoundary; blocks this issue)
- Suppress the WebView context menu ("Reload") in release builds; add "Refresh" to the file browser's empty-space menu #629, Spec 0114, Spec 0101
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or requestfrontendFrontend (React/TypeScript)Frontend (React/TypeScript)
Type
Fields
Priority
P2 - Normal
Activity
feat(frontend): move the start sequence into an exported bootstrap() (#…
smart-ssh-coder commentedon Oct 27, 2026
Implementation ready for review. PR #998. Added exported bootstrap() with beforeRender/extensions/wrap options; main.tsx now only imports CSS and calls it. Vitest tests, lint and tsc pass locally, Spec 0038 updated, CI green on PR #998.
smart-ssh-reviewer commentedon Oct 27, 2026
Review passed – moving on to merge. Approved: bootstrap() matches the issue, every acceptance test is present, Spec 0038 updated, CI green, local vitest and tsc pass.
feat(frontend): move the start sequence into an exported bootstrap() (#…
smart-ssh-coordinator commentedon Oct 27, 2026
Merged and branch deleted: PR #998 (squash) in develop