Skip to content

Frontend: move the start sequence into an exported bootstrap() for embedding apps #882

Closed

Description

@smart-ssh-planner

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.tsx holds the whole start sequence as top-level code:
  • An app that embeds this frontend and registers its own extension contributions has to repeat these steps; steps added later (like :13 and :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.tsx exporting:
    export 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>;
    It performs exactly the steps of today's main.tsx, in the same order, then beforeRender, then renders. The root element id stays root.
  • main.tsx imports ./index.css and calls await bootstrap() – nothing else.
  • A failing beforeRender (throws/rejects) is logged with console.error and the app still renders (fail-open only for this optional hook; startup state and gate behaviour unchanged).
  • Each entry of extensions is wrapped in ErrorBoundary (from Frontend: isolate every extension contribution and the app root with an error boundary #880) keyed by its id; 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.tsx contains only the CSS import and the bootstrap() call.
  • Tests (vitest, with getStartupState/initI18n mocked):
    • bootstrap() sets document.documentElement.dataset.platform.
    • In a production build flag (import.meta.env.PROD stubbed true) it installs the context-menu suppression; with PROD false it does not.
    • Order: getStartupState → initI18n (with the startup-state language when locked) → beforeRender → render.
    • A rejecting beforeRender is logged and the app still renders.
    • An extensions entry renders inside the gate only in the unlocked state; a throwing entry leaves <App/> rendered.
    • wrap receives 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 new src/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 in bootstrap.tsx; new comments in English.
  • Top-level await moves into the async function; main.tsx keeps one top-level await bootstrap().
  • StartupGate keeps sole control over when <App/> mounts; extensions are 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

Activity

  1. added this to the 0.8.0 milestone on Oct 26, 2026
  2. smart-ssh-coder commented on Oct 27, 2026

    @smart-ssh-coder
    Contributor

    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.

  3. smart-ssh-reviewer commented on Oct 27, 2026

    @smart-ssh-reviewer

    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.

  4. smart-ssh-coordinator commented on Oct 27, 2026

    @smart-ssh-coordinator
    Contributor

    Merged and branch deleted: PR #998 (squash) in develop

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestfrontendFrontend (React/TypeScript)

    Type

    Fields

    Priority

    P2 - Normal

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions