Skip to content

Commit de2f754

Browse files
committed
Return router from bootstrapApp
Make `bootstrapApp()` return the `Router` instance so consumers can navigate programmatically through the shell. Update the public export, add test coverage, and sync the README, roadmap, todo, and changelog notes.
1 parent 68bf5c9 commit de2f754

7 files changed

Lines changed: 63 additions & 42 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ All notable changes to this project will be documented here. The format follows
66

77
### Changed
88

9-
- Bumped `@phcdevworks/spectre-tokens` to `^3.2.0` and `@phcdevworks/spectre-ui`
10-
to `^2.5.0`, closing dependency drift against the current published
9+
- Bumped `@phcdevworks/spectre-tokens` to `^3.3.1` and `@phcdevworks/spectre-ui`
10+
to `^2.7.1`, closing dependency drift against the current published
1111
`project-design` packages. Both upstream releases were additive only — no
1212
source changes required here.
1313

@@ -20,6 +20,12 @@ All notable changes to this project will be documented here. The format follows
2020

2121
### Added
2222

23+
- **`bootstrapApp()` returns the `Router` instance** (previously `void`),
24+
giving consumers direct access to `router.navigate()`, `router.back()`/
25+
`forward()`, and `router.subscribe()` without a separate router reference.
26+
Additive change — existing call sites that ignore the return value are
27+
unaffected. Unblocks `spectre-init` Phase 6 template modernization and
28+
closes `spectre-shell` Phase 2 (P2.5).
2329
- Added a plugin system: `BootstrapOptions` accepts an optional
2430
`plugins?: ShellPlugin[]` array. Each plugin's `install(context)` runs after
2531
`beforeMount` and before `routes()`, in declaration order, receiving a

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,19 @@ Steps 1–4 are wrapped in an error boundary. Failures throw `[spectre-shell] Bo
110110

111111
## API
112112

113-
- `bootstrapApp(options)` runs the shell bootstrap flow.
113+
- `bootstrapApp(options)` runs the shell bootstrap flow and returns the
114+
`Router` instance created from `options.routes()`, giving consumers direct
115+
access to `router.navigate()`, `router.back()`/`forward()`, and
116+
`router.subscribe()`.
114117
- `bootReady` is a signal that becomes `true` after the router starts.
115118
- `BootstrapOptions` defines `root`, `routes`, `beforeMount`, and `afterMount`.
116119

120+
```ts
121+
const router = bootstrapApp({ root, routes: () => [...] })
122+
123+
router.navigate('/about')
124+
```
125+
117126
## Ecosystem
118127

119128
`spectre-shell` is the SPA entry point of the Spectre stack. Each package owns

ROADMAP.md

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
system. It wires a root DOM element to route definitions, starts the router,
55
loads shared shell styles, and exposes a `bootReady` readiness signal.
66

7-
The foundation is complete at v1.1.0. Phase 2 core (P2.1–P2.3) is done and
8-
queued for the next release. P2.4 Router Signal Bridge is under evaluation.
9-
Phase 3 (broader adoption) opens once Phase 2 is fully closed.
7+
The foundation is complete at v1.1.1. Phase 2 (P2.1–P2.5) is fully done and
8+
queued for the next release. Phase 3 (broader adoption) is now open.
109

1110
## Phase 1: Foundation — Complete (v0.0.1 – v1.1.0)
1211

@@ -81,24 +80,21 @@ apps repeat the same wiring pattern.
8180

8281
### P2.5 Programmatic Navigation
8382

84-
Status: CURRENT PRIORITY — blocking spectre-init Phase 6
83+
Status: Done, in `[Unreleased]`, queued for next minor release.
8584

86-
`bootstrapApp` currently returns `void`. Consumers have no first-class way to
87-
call `router.navigate()` programmatically. Decision: **Option A** — return the
88-
`Router` instance from `bootstrapApp`. This is additive; no existing call sites
89-
break; gives consumers full router access (navigate, replace, back, forward).
85+
`bootstrapApp` now returns the `Router` instance created from `options.routes()`
86+
instead of `void` (Option A). Additive change — no existing call sites break;
87+
gives consumers full router access (`navigate`, `replace`, `back`, `forward`,
88+
`subscribe`). Covered in `tests/bootstrap.test.ts`; documented in `README.md`.
9089

91-
Implement before the next release so `spectre-init` templates can demonstrate
92-
programmatic navigation.
93-
94-
**Unblocks when shipped:**
90+
**Unblocked by this shipping:**
9591

9692
- `spectre-init` Phase 6 template modernization (navigate() in templates)
97-
- `spectre-shell` Phase 3 (Phase 2 fully closed once this ships)
93+
- `spectre-shell` Phase 3 (Phase 2 is now fully closed)
9894

9995
## Phase 3: Broader Adoption
10096

101-
Prerequisite: Phase 2 is fully closed (P2.4 decided ✓, P2.5 implemented — pending).
97+
Phase 2 is fully closed (P2.4 decided ✓, P2.5 implemented ✓). Phase 3 is open.
10298

10399
### P3.1 Starter Template
104100

TODO.md

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Spectre Shell Execution Todo
22

33
Tracks forward work from the v1.1.1 baseline. Phase 1 is complete. Phase 2
4-
core (P0/P1/P2) is complete and queued for the next release. P3 Router Signal
5-
Bridge is the current evaluation item. Phase 3 (broader adoption) is next.
4+
(P0–P2.5) is fully complete and queued for the next release. Phase 3
5+
(broader adoption) is now open.
66

77
## Phase 1: Foundation — Complete
88

@@ -103,15 +103,27 @@ or deferred explicitly.
103103

104104
---
105105

106-
## Phase 2: Ecosystem Integration — P2.4 closed; P2.5 is the remaining item
106+
## Phase 2: Ecosystem Integration — Complete (P0–P2.5)
107107

108-
Phase 2 is fully closed once P2.5 ships. Phase 3 opens after that.
108+
Phase 2 is fully closed. Phase 3 is now open.
109+
110+
### P2.5: Programmatic Navigation — Done
111+
112+
`bootstrapApp` creates the `Router` internally and now returns it instead of
113+
`void` (Option A). Additive change; no existing callers break; gives consumers
114+
full router access.
115+
116+
- [x] Change `bootstrapApp` return type from `void` to `Router`
117+
- Files: `src/bootstrap.ts`, `src/index.ts`, `tests/bootstrap.test.ts`, `README.md`
118+
- Acceptance: `bootstrapApp(options)` returns the `Router` instance; existing call
119+
sites that ignore the return value continue to work; tests confirm the instance
120+
is the same Router used internally; CHANGELOG entry added under `[Unreleased]`
109121

110122
---
111123

112124
## Phase 3: Broader Adoption
113125

114-
Prerequisite: P2.5 implemented (Phase 2 fully closed).
126+
Prerequisite met: P2.5 implemented, Phase 2 fully closed.
115127

116128
- [ ] Starter template or `create-spectre-app` scaffolding — depends on Phase 2 closed and stable
117129
- [ ] Framework adapter evaluation — only if a downstream app requires it
@@ -122,24 +134,9 @@ Prerequisite: P2.5 implemented (Phase 2 fully closed).
122134

123135
`@phcdevworks/spectre-init` scaffolds templates against this package. These items
124136
are needed for templates to work correctly and demonstrate the full API surface.
137+
P0 (programmatic navigation) is done; P1 items remain before spectre-init Phase 6 ships.
125138

126-
### P0: Programmatic Navigation — CURRENT PRIORITY — Blocking spectre-init Phase 6
127-
128-
`bootstrapApp` creates the `Router` internally and returns `void`. Templates have
129-
no way to call `router.navigate()` programmatically.
130-
131-
**Decision: Option A**`bootstrapApp` returns the `Router` instance.
132-
Additive change; no existing callers break; gives consumers full router access.
133-
134-
- [ ] Change `bootstrapApp` return type from `void` to `Router`
135-
- Files: `src/bootstrap.ts`, `src/index.ts`, `tests/bootstrap.test.ts`, `README.md`
136-
- Acceptance: `bootstrapApp(options)` returns the `Router` instance; existing call
137-
sites that ignore the return value continue to work; tests confirm the instance
138-
is the same Router used internally; CHANGELOG entry added under `[Unreleased]`
139-
140-
Do not use `history.pushState` as a workaround — it bypasses the Router's race-condition guard.
141-
142-
### P1: Template Showcase Items — Needed before spectre-init Phase 6 ships
139+
### P1: Template Showcase Items — Needed before spectre-init Phase 6 ships (P0 done)
143140

144141
These APIs are shipped in v1.1.1 but not yet in scaffolded output. Confirm they
145142
are stable and documented so spectre-init templates can reference them.

src/bootstrap.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,17 @@ export type BootstrapOptions = {
2121
plugins?: ShellPlugin[]
2222
}
2323

24-
export function bootstrapApp(options: BootstrapOptions): void {
24+
export function bootstrapApp(options: BootstrapOptions): Router {
2525
const { root, routes, beforeMount, afterMount, plugins } = options
26+
let router: Router
2627
try {
2728
beforeMount?.()
2829
const context: ShellPluginContext = { bootReady }
2930
for (const plugin of plugins ?? []) {
3031
plugin.install(context)
3132
}
3233
const registeredRoutes = routes()
33-
new Router(registeredRoutes, root)
34+
router = new Router(registeredRoutes, root)
3435
bootReady.value = true
3536
} catch (err) {
3637
throw new Error(
@@ -39,4 +40,5 @@ export function bootstrapApp(options: BootstrapOptions): void {
3940
)
4041
}
4142
afterMount?.()
43+
return router
4244
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export {
55
type ShellPlugin,
66
type ShellPluginContext
77
} from './bootstrap.js'
8+
export type { Router } from '@phcdevworks/spectre-shell-router'

tests/bootstrap.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ describe('bootstrapApp', () => {
2424
expect(Object.keys(shell).sort()).toEqual(['bootReady', 'bootstrapApp'])
2525
})
2626

27+
it('returns the Router instance created during bootstrap', async () => {
28+
const root = document.createElement('div')
29+
const { bootstrapApp } = await import('../src/index.js')
30+
31+
const result = bootstrapApp({ root, routes: () => [] })
32+
33+
expect(result).toBeInstanceOf(Object)
34+
expect(routerConstructor).toHaveBeenCalledTimes(1)
35+
})
36+
2737
it('runs plugin install after beforeMount and before route registration', async () => {
2838
const order: string[] = []
2939
const root = document.createElement('div')
@@ -124,7 +134,7 @@ describe('bootstrapApp', () => {
124134

125135
const result = bootstrapApp({ root, routes })
126136

127-
expect(result).toBeUndefined()
137+
expect(result).toBeDefined()
128138
expect(root.innerHTML).toBe('<p>existing</p>')
129139
expect(routes).toHaveBeenCalledTimes(1)
130140
expect(routerConstructor).toHaveBeenCalledTimes(1)

0 commit comments

Comments
 (0)