bitwrench 2.0.22 → 2.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE.txt +1 -1
  2. package/README.md +4 -3
  3. package/bin/bwmcp.js +3 -0
  4. package/dist/bitwrench-bccl.cjs.js +1 -1
  5. package/dist/bitwrench-bccl.cjs.min.js +1 -1
  6. package/dist/bitwrench-bccl.cjs.min.js.gz +0 -0
  7. package/dist/bitwrench-bccl.esm.js +1 -1
  8. package/dist/bitwrench-bccl.esm.min.js +1 -1
  9. package/dist/bitwrench-bccl.esm.min.js.gz +0 -0
  10. package/dist/bitwrench-bccl.umd.js +1 -1
  11. package/dist/bitwrench-bccl.umd.min.js +1 -1
  12. package/dist/bitwrench-bccl.umd.min.js.gz +0 -0
  13. package/dist/bitwrench-code-edit.cjs.js +1 -1
  14. package/dist/bitwrench-code-edit.cjs.min.js +1 -1
  15. package/dist/bitwrench-code-edit.es5.js +1 -1
  16. package/dist/bitwrench-code-edit.es5.min.js +1 -1
  17. package/dist/bitwrench-code-edit.esm.js +1 -1
  18. package/dist/bitwrench-code-edit.esm.min.js +1 -1
  19. package/dist/bitwrench-code-edit.umd.js +1 -1
  20. package/dist/bitwrench-code-edit.umd.min.js +1 -1
  21. package/dist/bitwrench-code-edit.umd.min.js.gz +0 -0
  22. package/dist/bitwrench-debug.js +1 -1
  23. package/dist/bitwrench-debug.min.js +1 -1
  24. package/dist/bitwrench-lean.cjs.js +3 -3
  25. package/dist/bitwrench-lean.cjs.min.js +2 -2
  26. package/dist/bitwrench-lean.cjs.min.js.gz +0 -0
  27. package/dist/bitwrench-lean.es5.js +3 -3
  28. package/dist/bitwrench-lean.es5.min.js +2 -2
  29. package/dist/bitwrench-lean.es5.min.js.gz +0 -0
  30. package/dist/bitwrench-lean.esm.js +3 -3
  31. package/dist/bitwrench-lean.esm.min.js +2 -2
  32. package/dist/bitwrench-lean.esm.min.js.gz +0 -0
  33. package/dist/bitwrench-lean.umd.js +3 -3
  34. package/dist/bitwrench-lean.umd.min.js +2 -2
  35. package/dist/bitwrench-lean.umd.min.js.gz +0 -0
  36. package/dist/bitwrench-util-css.cjs.js +1 -1
  37. package/dist/bitwrench-util-css.cjs.min.js +1 -1
  38. package/dist/bitwrench-util-css.es5.js +1 -1
  39. package/dist/bitwrench-util-css.es5.min.js +1 -1
  40. package/dist/bitwrench-util-css.esm.js +1 -1
  41. package/dist/bitwrench-util-css.esm.min.js +1 -1
  42. package/dist/bitwrench-util-css.umd.js +1 -1
  43. package/dist/bitwrench-util-css.umd.min.js +1 -1
  44. package/dist/bitwrench-util-css.umd.min.js.gz +0 -0
  45. package/dist/bitwrench.cjs.js +3 -3
  46. package/dist/bitwrench.cjs.min.js +2 -2
  47. package/dist/bitwrench.cjs.min.js.gz +0 -0
  48. package/dist/bitwrench.css +1 -1
  49. package/dist/bitwrench.es5.js +3 -3
  50. package/dist/bitwrench.es5.min.js +2 -2
  51. package/dist/bitwrench.es5.min.js.gz +0 -0
  52. package/dist/bitwrench.esm.js +3 -3
  53. package/dist/bitwrench.esm.min.js +2 -2
  54. package/dist/bitwrench.esm.min.js.gz +0 -0
  55. package/dist/bitwrench.umd.js +3 -3
  56. package/dist/bitwrench.umd.min.js +2 -2
  57. package/dist/bitwrench.umd.min.js.gz +0 -0
  58. package/dist/builds.json +57 -57
  59. package/dist/bwserve.cjs.js +2 -2
  60. package/dist/bwserve.esm.js +2 -2
  61. package/dist/sri.json +45 -45
  62. package/docs/README.md +76 -0
  63. package/docs/app-patterns.md +264 -0
  64. package/docs/bitwrench-mcp.md +426 -0
  65. package/docs/bitwrench_api.md +2232 -0
  66. package/docs/bw-attach.md +399 -0
  67. package/docs/bwserve.md +841 -0
  68. package/docs/cli.md +307 -0
  69. package/docs/component-cheatsheet.md +144 -0
  70. package/docs/component-library.md +1099 -0
  71. package/docs/framework-translation-table.md +33 -0
  72. package/docs/llm-bitwrench-guide.md +672 -0
  73. package/docs/routing.md +562 -0
  74. package/docs/state-management.md +767 -0
  75. package/docs/taco-format.md +373 -0
  76. package/docs/theming.md +309 -0
  77. package/docs/thinking-in-bitwrench.md +1457 -0
  78. package/docs/tutorial-bwserve.md +297 -0
  79. package/docs/tutorial-embedded.md +314 -0
  80. package/docs/tutorial-website.md +255 -0
  81. package/package.json +11 -3
  82. package/readme.html +5 -4
  83. package/src/mcp/knowledge.js +231 -0
  84. package/src/mcp/live.js +226 -0
  85. package/src/mcp/server.js +216 -0
  86. package/src/mcp/tools.js +369 -0
  87. package/src/mcp/transport.js +55 -0
  88. package/src/version.js +3 -3
@@ -0,0 +1,562 @@
1
+ # Client-Side Routing
2
+
3
+ ## What Is Client-Side Routing?
4
+
5
+ In a traditional website, every page is a separate HTML file on the server. When you click a link, the browser sends a request, the server sends back a new page, and the entire screen refreshes. Every navigation is a full page load.
6
+
7
+ Client-side routing eliminates that round trip. A single HTML page loads once. When the user navigates, JavaScript intercepts the URL change, figures out which "view" to show, and swaps content in the DOM -- no server request, no page reload. The URL in the address bar still changes (so bookmarks and back/forward work), but the page never fully reloads.
8
+
9
+ This is what makes a Single-Page Application (SPA).
10
+
11
+ ### How URLs Work in an SPA
12
+
13
+ The browser has two mechanisms that SPAs exploit:
14
+
15
+ **Hash fragment** -- The part of the URL after `#`. Changing the hash does not trigger a page reload. The browser fires a `hashchange` event that JavaScript can listen to. Example: `http://example.com/#/users/123`
16
+
17
+ **History API** -- `history.pushState()` changes the URL in the address bar without a page reload. The browser fires a `popstate` event when the user hits back/forward. Example: `http://example.com/users/123` (looks like a normal URL, but the page never reloaded)
18
+
19
+ Bitwrench supports both. Hash mode is the default because it works everywhere with zero server configuration.
20
+
21
+ ### What Happens When You Navigate
22
+
23
+ Here is the full sequence when a user clicks a link or calls `bw.navigate()`:
24
+
25
+ ```
26
+ User clicks link
27
+ |
28
+ v
29
+ 1. URL changes (hash or pushState)
30
+ |
31
+ v
32
+ 2. Router reads the new URL
33
+ |
34
+ v
35
+ 3. before() guard runs (can redirect or block)
36
+ |
37
+ v
38
+ 4. URL is split into segments and matched against route patterns
39
+ |
40
+ v
41
+ 5. Matched handler is called with extracted params
42
+ |
43
+ v
44
+ 6. Handler returns a TACO object (the "page")
45
+ |
46
+ v
47
+ 7. bw.DOM(target, taco) replaces the content area
48
+ |
49
+ v
50
+ 8. bw.pub('bw:route', data) notifies subscribers
51
+ |
52
+ v
53
+ 9. after() hook runs (analytics, scroll reset, etc.)
54
+ ```
55
+
56
+ The key insight: "switching pages" is just calling `bw.DOM()` with different TACO content. There is no page object, no component lifecycle to manage. A "page" is a function that returns TACO.
57
+
58
+ ---
59
+
60
+ ## Quick Start
61
+
62
+ ```javascript
63
+ bw.router({
64
+ target: '#app',
65
+ routes: {
66
+ '/': function() { return { t: 'h1', c: 'Home' }; },
67
+ '/about': function() { return { t: 'h1', c: 'About' }; },
68
+ '/users/:id': function(params) {
69
+ return { t: 'div', c: 'User ' + params.id };
70
+ },
71
+ '*': function() { return { t: 'h1', c: '404 Not Found' }; }
72
+ }
73
+ });
74
+ ```
75
+
76
+ The router reads the current URL, matches a route, calls the handler, and renders the result into `#app`. It listens for URL changes (back/forward, hash changes) and re-renders automatically.
77
+
78
+ ---
79
+
80
+ ## How Route Matching Works
81
+
82
+ When the URL changes, the router must decide which handler to call. This happens in three steps:
83
+
84
+ ### Step 1: Normalize the URL
85
+
86
+ The raw URL is cleaned up before matching:
87
+ - Query string is stripped and parsed separately (available as `params._query`)
88
+ - Double slashes are collapsed (`//users///` becomes `/users`)
89
+ - Trailing slashes are removed (`/users/` becomes `/users`)
90
+ - Empty paths become `/`
91
+
92
+ ### Step 2: Split into segments
93
+
94
+ The normalized path is split on `/` into segments:
95
+
96
+ ```
97
+ /users/123/posts => ['', 'users', '123', 'posts']
98
+ / => ['']
99
+ ```
100
+
101
+ ### Step 3: Match against route patterns
102
+
103
+ Routes are checked in this priority order:
104
+
105
+ **1. Exact match** -- Every segment matches literally.
106
+ ```
107
+ Pattern: /users/new URL: /users/new => MATCH
108
+ Pattern: /users/new URL: /users/123 => no match
109
+ ```
110
+
111
+ **2. Parameterized match** -- Segments starting with `:` capture any value. Segment count must match exactly.
112
+ ```
113
+ Pattern: /users/:id URL: /users/123 => MATCH, params.id = '123'
114
+ Pattern: /users/:id/posts URL: /users/123 => no match (segment count differs)
115
+ Pattern: /users/:id/posts/:pid URL: /users/42/posts/7 => MATCH, params = {id:'42', pid:'7'}
116
+ ```
117
+
118
+ **3. Catch-all** -- Patterns ending with `/*` match any number of trailing segments. The captured portion goes into `params._rest`.
119
+ ```
120
+ Pattern: /docs/* URL: /docs/api/colors => MATCH, params._rest = 'api/colors'
121
+ Pattern: /admin/:section/* URL: /admin/users/123/edit => MATCH, params = {section:'users', _rest:'123/edit'}
122
+ ```
123
+
124
+ **4. Global wildcard** -- The pattern `*` (by itself) matches anything not matched above. This is your 404 handler.
125
+ ```
126
+ Pattern: * URL: /anything => MATCH
127
+ ```
128
+
129
+ Within the same priority level, routes are checked in the order you registered them. **Register specific routes before general ones:**
130
+
131
+ ```javascript
132
+ routes: {
133
+ '/users/new': newUserPage, // checked first (exact)
134
+ '/users/:id': userDetailPage, // checked second (parameterized)
135
+ '*': notFoundPage // checked last (wildcard)
136
+ }
137
+ ```
138
+
139
+ ### Query Strings
140
+
141
+ Query strings are parsed but do not affect which route matches:
142
+
143
+ ```javascript
144
+ // URL: /users/123?tab=posts&page=2
145
+ '/users/:id': function(params) {
146
+ params.id; // '123'
147
+ params._query.tab; // 'posts'
148
+ params._query.page; // '2'
149
+ }
150
+ ```
151
+
152
+ ### Full Pattern Reference
153
+
154
+ | Pattern | Example URL | Params |
155
+ |---------|-------------|--------|
156
+ | `/` | `/` | `{}` |
157
+ | `/users` | `/users` | `{}` |
158
+ | `/users/:id` | `/users/123` | `{ id: '123' }` |
159
+ | `/users/:id/posts/:pid` | `/users/42/posts/7` | `{ id: '42', pid: '7' }` |
160
+ | `/docs/*` | `/docs/api/colors` | `{ _rest: 'api/colors' }` |
161
+ | `/admin/:section/*` | `/admin/users/123/edit` | `{ section: 'users', _rest: '123/edit' }` |
162
+ | `*` | `/anything` | `{}` |
163
+
164
+ ---
165
+
166
+ ## Hash Mode vs History Mode
167
+
168
+ ### Hash mode (default)
169
+
170
+ URLs look like `http://example.com/#/users/123`.
171
+
172
+ ```javascript
173
+ bw.router({
174
+ mode: 'hash', // default, can be omitted
175
+ target: '#app',
176
+ routes: { ... }
177
+ });
178
+ ```
179
+
180
+ **How it works:** The router sets `window.location.hash` and listens for `hashchange` events. The hash is never sent to the server, so no server configuration is needed.
181
+
182
+ **Pros:** Works everywhere (including old browsers). No server config. Files can be opened from disk (`file://`).
183
+
184
+ **Cons:** URLs have a `#` in them. Some people find this ugly.
185
+
186
+ ### History mode
187
+
188
+ URLs look like `http://example.com/users/123` -- clean, no `#`.
189
+
190
+ ```javascript
191
+ bw.router({
192
+ mode: 'history',
193
+ base: '/app', // optional: strip this prefix before matching
194
+ target: '#app',
195
+ routes: { ... }
196
+ });
197
+ ```
198
+
199
+ **How it works:** The router calls `history.pushState()` to change the URL and listens for `popstate` events (back/forward buttons).
200
+
201
+ **Pros:** Clean URLs. Looks like a traditional website.
202
+
203
+ **Cons:** Requires server configuration. The server must return your `index.html` for all routes, because if a user bookmarks `/users/123` and visits it directly, the server needs to serve the SPA shell (not a 404). This is called "SPA fallback" or "history fallback."
204
+
205
+ With `base: '/app'`, a URL like `http://example.com/app/users/123` is matched as `/users/123`.
206
+
207
+ ---
208
+
209
+ ## Route Handlers
210
+
211
+ Handlers are plain functions. They receive a `params` object and return a TACO (or null).
212
+
213
+ ```javascript
214
+ '/users/:id': function(params) {
215
+ return bw.makeCard({
216
+ title: 'User ' + params.id,
217
+ content: 'Tab: ' + (params._query.tab || 'profile')
218
+ });
219
+ }
220
+ ```
221
+
222
+ If a handler returns `null` or `undefined`, the target element is not updated. This is useful for routes that only need side effects (analytics, logging).
223
+
224
+ If no `target` is configured, handlers still run and pub/sub events still fire -- useful for pub/sub-only routing where you manage rendering yourself.
225
+
226
+ ### Stateful route handlers
227
+
228
+ Route handlers can return stateful TACOs with `o.state` and `o.render`:
229
+
230
+ ```javascript
231
+ function dashboard() {
232
+ return {
233
+ t: 'div',
234
+ o: {
235
+ state: { data: null },
236
+ mounted: function(el) {
237
+ fetch('/api/stats').then(function(r) { return r.json(); })
238
+ .then(function(d) { el._bw_state.data = d; bw.update(el); });
239
+ },
240
+ render: function(el) {
241
+ var s = el._bw_state;
242
+ bw.DOM(el, s.data
243
+ ? bw.makeTable({ data: s.data, sortable: true })
244
+ : { t: 'p', c: 'Loading...' }
245
+ );
246
+ }
247
+ }
248
+ };
249
+ }
250
+ ```
251
+
252
+ When the router mounts this TACO, `mounted` fires, fetches data, updates state, and triggers a re-render. The route handler is just a function that returns any valid TACO -- the full component model (Level 0 through Level 2) is available.
253
+
254
+ ---
255
+
256
+ ## Navigation
257
+
258
+ ### bw.navigate(path, opts)
259
+
260
+ Programmatic navigation. Delegates to the active router.
261
+
262
+ ```javascript
263
+ bw.navigate('/users/123');
264
+ bw.navigate('/users/123', { replace: true }); // replace history entry (no back)
265
+ bw.navigate('/search?q=hello'); // query strings preserved
266
+ ```
267
+
268
+ ### bw.link(path, content, attrs)
269
+
270
+ Returns a TACO `<a>` element with navigation wired up:
271
+
272
+ ```javascript
273
+ bw.link('/about', 'About Us', { class: 'nav-item' })
274
+ // Returns:
275
+ // { t: 'a', a: { href: '#/about', class: 'nav-item', onclick: ... }, c: 'About Us' }
276
+ ```
277
+
278
+ The `onclick` handler calls `e.preventDefault()` and `bw.navigate(path)`. The `href` is set to `#` + path (hash mode) so right-click "copy link" and middle-click "open in new tab" still work.
279
+
280
+ Use `bw.link()` instead of raw `<a>` tags for navigation within the SPA. External links (to other sites) should use normal TACO anchors: `{ t: 'a', a: { href: 'https://...' }, c: 'External' }`.
281
+
282
+ ---
283
+
284
+ ## Guards and Hooks
285
+
286
+ ### before(toPath, fromPath)
287
+
288
+ Called before each navigation. Use it for authentication checks, redirects, or blocking.
289
+
290
+ ```javascript
291
+ bw.router({
292
+ target: '#app',
293
+ routes: { ... },
294
+ before: function(to, from) {
295
+ // Redirect: return a path string
296
+ if (to === '/admin' && !isLoggedIn) return '/login';
297
+
298
+ // Block: return false
299
+ if (to === '/locked') return false;
300
+
301
+ // Allow: return anything else (undefined, null, true)
302
+ }
303
+ });
304
+ ```
305
+
306
+ | Return value | Effect |
307
+ |-------------|--------|
308
+ | `string` | Redirect to that path |
309
+ | `false` | Block navigation (URL and view unchanged) |
310
+ | anything else | Allow navigation |
311
+
312
+ ### after(toPath, fromPath)
313
+
314
+ Called after each navigation completes. Use it for analytics, logging, scroll reset.
315
+
316
+ ```javascript
317
+ after: function(to, from) {
318
+ window.scrollTo(0, 0);
319
+ }
320
+ ```
321
+
322
+ ---
323
+
324
+ ## Pub/Sub Integration
325
+
326
+ Every route change publishes a `bw:route` event:
327
+
328
+ ```javascript
329
+ bw.sub('bw:route', function(data) {
330
+ // data.path -- current path (e.g., '/users/123')
331
+ // data.params -- matched params (e.g., { id: '123', _query: {} })
332
+ // data.query -- parsed query string object
333
+ // data.from -- previous path
334
+ });
335
+ ```
336
+
337
+ This lets any component react to route changes without being coupled to the router. Common uses:
338
+
339
+ **Highlight active nav item:**
340
+ ```javascript
341
+ bw.sub('bw:route', function(data) {
342
+ navEl.bw.setActive(data.path);
343
+ }, navEl); // auto-unsubscribes when navEl is removed
344
+ ```
345
+
346
+ **Update page title:**
347
+ ```javascript
348
+ var titles = { '/': 'Home', '/about': 'About', '/contact': 'Contact' };
349
+ bw.sub('bw:route', function(data) {
350
+ document.title = titles[data.path] || 'My App';
351
+ });
352
+ ```
353
+
354
+ **Log analytics:**
355
+ ```javascript
356
+ bw.sub('bw:route', function(data) {
357
+ analytics.pageView(data.path);
358
+ });
359
+ ```
360
+
361
+ ---
362
+
363
+ ## Patterns
364
+
365
+ ### SPA with persistent nav and footer
366
+
367
+ The router only controls the content area. Nav and footer are mounted separately and persist across route changes:
368
+
369
+ ```html
370
+ <div id="nav-root"></div>
371
+ <div id="app"></div>
372
+ <div id="footer-root"></div>
373
+ ```
374
+
375
+ ```javascript
376
+ // Mount persistent UI
377
+ bw.DOM('#nav-root', makeNavBar());
378
+ bw.DOM('#footer-root', makeFooter());
379
+
380
+ // Router only swaps #app content
381
+ bw.router({
382
+ target: '#app',
383
+ routes: {
384
+ '/': homePage,
385
+ '/about': aboutPage,
386
+ '/contact': contactPage,
387
+ '*': notFoundPage
388
+ }
389
+ });
390
+ ```
391
+
392
+ ### Nav bar that highlights the active route
393
+
394
+ ```javascript
395
+ function makeNav() {
396
+ var links = [
397
+ { path: '/', label: 'Home' },
398
+ { path: '/about', label: 'About' },
399
+ { path: '/contact', label: 'Contact' }
400
+ ];
401
+ return {
402
+ t: 'nav',
403
+ o: {
404
+ state: { active: '/' },
405
+ mounted: function(el) {
406
+ bw.sub('bw:route', function(d) {
407
+ el._bw_state.active = d.path;
408
+ bw.update(el);
409
+ }, el);
410
+ },
411
+ render: function(el) {
412
+ var s = el._bw_state;
413
+ bw.DOM(el, {
414
+ t: 'ul', c: links.map(function(link) {
415
+ return { t: 'li', a: {
416
+ style: link.path === s.active ? 'font-weight:bold' : ''
417
+ }, c: bw.link(link.path, link.label) };
418
+ })
419
+ });
420
+ }
421
+ }
422
+ };
423
+ }
424
+ ```
425
+
426
+ ### Auth guard with login redirect
427
+
428
+ ```javascript
429
+ var isLoggedIn = false;
430
+
431
+ bw.router({
432
+ target: '#app',
433
+ routes: {
434
+ '/': homePage,
435
+ '/login': loginPage,
436
+ '/dashboard': dashboardPage,
437
+ '*': notFoundPage
438
+ },
439
+ before: function(to) {
440
+ if (to === '/dashboard' && !isLoggedIn) return '/login';
441
+ }
442
+ });
443
+ ```
444
+
445
+ ### Router with shared state store
446
+
447
+ Combine the router with a store pattern for multi-view apps. The store holds data; views subscribe to the slices they need:
448
+
449
+ ```javascript
450
+ var store = { users: [], stats: {} };
451
+
452
+ function updateStore(key, value) {
453
+ store[key] = value;
454
+ bw.pub('store:' + key, value);
455
+ }
456
+
457
+ function usersPage() {
458
+ return {
459
+ t: 'div',
460
+ o: {
461
+ state: {},
462
+ mounted: function(el) {
463
+ bw.sub('store:users', function() { bw.update(el); }, el);
464
+ },
465
+ render: function(el) {
466
+ bw.DOM(el, bw.makeTable({
467
+ data: store.users,
468
+ columns: ['name', 'role', 'status'],
469
+ sortable: true
470
+ }));
471
+ }
472
+ }
473
+ };
474
+ }
475
+
476
+ bw.router({
477
+ target: '#app',
478
+ routes: {
479
+ '/': overviewPage,
480
+ '/users': usersPage,
481
+ '*': notFoundPage
482
+ }
483
+ });
484
+ ```
485
+
486
+ See [State Management: Shared State Across Views](state-management.md#shared-state-across-views) for details on the store pattern.
487
+
488
+ ### Complementing bwserve
489
+
490
+ The client router complements bwserve's server-side `app.page()`. Use bwserve for top-level page delivery and the client router for sub-navigation within a page:
491
+
492
+ ```javascript
493
+ // Server handles top-level pages
494
+ app.page('/dashboard', function(client) {
495
+ client.render('#app', dashboardShell());
496
+ });
497
+
498
+ // Client handles tab navigation within the dashboard
499
+ bw.router({
500
+ target: '#dashboard-content',
501
+ mode: 'hash',
502
+ routes: {
503
+ '/overview': overviewTab,
504
+ '/analytics': analyticsTab,
505
+ '/settings': settingsTab
506
+ }
507
+ });
508
+ ```
509
+
510
+ ---
511
+
512
+ ## Cleanup
513
+
514
+ Call `r.destroy()` to remove event listeners and stop the router:
515
+
516
+ ```javascript
517
+ var r = bw.router({ ... });
518
+
519
+ // Later, when done:
520
+ r.destroy();
521
+ ```
522
+
523
+ After `destroy()`, `bw.navigate()` calls are no-ops and no more `bw:route` events are published.
524
+
525
+ ---
526
+
527
+ ## API Summary
528
+
529
+ | Function | Description |
530
+ |----------|-------------|
531
+ | `bw.router(config)` | Create and start a router. Returns `{ navigate, current, destroy }` |
532
+ | `bw.navigate(path, opts)` | Programmatic navigation (delegates to active router) |
533
+ | `bw.link(path, content, attrs)` | Returns TACO `<a>` with navigation wired |
534
+ | `bw:route` (pub/sub topic) | Published on every route change with `{ path, params, query, from }` |
535
+
536
+ ### bw.router(config) options
537
+
538
+ | Option | Type | Default | Description |
539
+ |--------|------|---------|-------------|
540
+ | `routes` | `Object` | (required) | Map of route patterns to handler functions |
541
+ | `target` | `string` | `null` | CSS selector where handler output is mounted via `bw.DOM()` |
542
+ | `mode` | `string` | `'hash'` | `'hash'` or `'history'` |
543
+ | `base` | `string` | `'/'` | Base path to strip in history mode |
544
+ | `before` | `function` | `null` | Guard called before each navigation |
545
+ | `after` | `function` | `null` | Hook called after each navigation |
546
+
547
+ ### Router object methods
548
+
549
+ | Method | Description |
550
+ |--------|-------------|
551
+ | `r.navigate(path, opts)` | Navigate to a path (same as `bw.navigate()`) |
552
+ | `r.current()` | Returns `{ path, params, query }` for current route |
553
+ | `r.destroy()` | Remove listeners, stop routing |
554
+
555
+ ---
556
+
557
+ ## Related
558
+
559
+ - [App Patterns](app-patterns.md) -- Multi-Page SPA pattern with router + shared state
560
+ - [State Management](state-management.md) -- Three-level component model, store pattern
561
+ - [Component Cheat Sheet](component-cheatsheet.md) -- All 50+ components at a glance
562
+ - [examples/dashboard-spa/](../examples/dashboard-spa/) -- Working SPA with 4 routed views