pict-section-inlinedocumentation 1.0.0 → 1.0.2

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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -13
  3. package/diagrams/four-levels-of-embeddedness.excalidraw +717 -0
  4. package/diagrams/four-levels-of-embeddedness.mmd +4 -0
  5. package/diagrams/four-levels-of-embeddedness.svg +2 -0
  6. package/docs/README.md +10 -19
  7. package/docs/_brand.json +18 -0
  8. package/docs/_cover.md +6 -0
  9. package/docs/_sidebar.md +8 -4
  10. package/docs/_version.json +3 -3
  11. package/docs/architecture.md +2 -44
  12. package/docs/diagrams/component-diagram.excalidraw +3546 -0
  13. package/docs/diagrams/component-diagram.mmd +42 -0
  14. package/docs/diagrams/component-diagram.svg +2 -0
  15. package/docs/diagrams/four-levels-of-embeddedness.excalidraw +717 -0
  16. package/docs/diagrams/four-levels-of-embeddedness.mmd +9 -0
  17. package/docs/diagrams/four-levels-of-embeddedness.svg +2 -0
  18. package/docs/diagrams/project-layout.excalidraw +6082 -0
  19. package/docs/diagrams/project-layout.mmd +25 -0
  20. package/docs/diagrams/project-layout.svg +2 -0
  21. package/docs/embedding-level2-routes.md +1 -1
  22. package/docs/embedding-level4-autogen.md +1 -1
  23. package/docs/examples/README.md +9 -0
  24. package/docs/examples/bookshop/README.md +582 -0
  25. package/docs/examples/bookshop/bookshop_example.js +4519 -0
  26. package/docs/examples/bookshop/index.html +236 -0
  27. package/docs/index.html +6 -7
  28. package/docs/overview.md +4 -4
  29. package/docs/reference.md +2 -27
  30. package/docs/retold-catalog.json +77 -178
  31. package/docs/retold-keyword-index.json +14134 -1917
  32. package/example_applications/basic/docs/_brand.json +18 -0
  33. package/example_applications/bookshop/docs/_brand.json +18 -0
  34. package/example_applications/bookshop/package.json +9 -1
  35. package/package.json +9 -9
  36. package/source/providers/Pict-Provider-InlineDocumentation.js +27 -3
  37. package/source/views/Pict-View-InlineDocumentation-Content.js +19 -15
  38. package/source/views/Pict-View-InlineDocumentation-Layout.js +115 -3
  39. package/source/views/Pict-View-InlineDocumentation-Nav.js +98 -17
  40. package/source/views/Pict-View-InlineDocumentation-TopicManager.js +18 -18
  41. package/docs/css/docuserve.css +0 -327
@@ -0,0 +1,582 @@
1
+ # Bookshop - Inline Contextual Documentation in Practice
2
+
3
+ <!-- docuserve:example-launch:start -->
4
+ > **[Launch the live app](examples/bookshop/index.html)** - runs in your browser, opens in a new tab.
5
+ <!-- docuserve:example-launch:end -->
6
+
7
+ A small e-commerce demo that puts the documentation **right next to the
8
+ UI it describes**. Browse a book catalog, click a book to open its
9
+ store page, and at every step the right-hand help panel knows which
10
+ route you're on and what to show. Hover an icon for a markdown
11
+ tooltip, press **F1** to toggle the whole panel, click a `?` button
12
+ next to a section header to jump to a specific topic - all backed by
13
+ one provider, one topics manifest, and a folder of plain markdown
14
+ files.
15
+
16
+ This is the reference example for **embedding levels 3 and 4** from
17
+ the inline-documentation module: route-mapped contextual help, plus
18
+ data-attribute-driven tooltips that resolve content from the same
19
+ topics manifest. The application code does no markdown parsing of its
20
+ own - the provider owns everything from file loading to tooltip
21
+ positioning.
22
+
23
+ ## What it demonstrates
24
+
25
+ | Capability | Where you see it |
26
+ |------------|------------------|
27
+ | Inline-documentation provider registered as `Pict-InlineDocumentation` | `addProvider('Pict-InlineDocumentation', libInlineDocumentation.default_configuration, libInlineDocumentation)` |
28
+ | Topics manifest pre-loaded into AppData | `AppData.InlineDocumentation.Topics = _TopicsData` before `initializeDocumentation()` |
29
+ | Route-mapped help panel | `tmpDocProvider.navigateToRoute('/books/store/' + pBookID)` switches the topic in the help panel |
30
+ | `data-d-tooltip` attribute -> markdown tooltip | `<span data-d-tooltip="catalog-info" data-d-tooltip-icon></span>` - provider scans on render, attaches hover/focus tooltips |
31
+ | Topic-keyed help buttons | `tmpApp.showHelp('BOOKSHOP-STORE')` triggers a specific topic regardless of current route |
32
+ | F1 keyboard shortcut for the help panel | `document.addEventListener('keydown', ...)` in `_setupKeyboardShortcuts()` |
33
+ | `initializeDocumentation` with save/upload handlers | Demo wires `onSave`, `onTopicsSave`, `onImageUpload` callbacks the provider invokes |
34
+ | Edit-mode toggle | `tmpDocProvider.setEditEnabled(true)` lets the demo edit the topic markdown live |
35
+ | `loadTopicDocument(topicCode)` API | The application's `showHelp(topicCode)` reaches into the provider to swap topics on demand |
36
+ | Markdown-with-help-links | `[Book Catalog Help](help:book-list.md)` - `help:` URLs resolve via the documentation provider, not the browser |
37
+ | Wildcard route matching | `"Routes": ["/books/store/*"]` matches `/books/store/1`, `/books/store/2`, ... |
38
+ | Tooltips co-located with topics | Each topic record carries a `Tooltips: {...}` block that the provider auto-registers |
39
+
40
+ ## Key files
41
+
42
+ - `Pict-Application-Bookshop.js` - the application. Registers the
43
+ inline-documentation provider, three views (BookList, Store,
44
+ HelpToggle), bootstraps `AppData.Bookshop` + `AppData.InlineDocumentation`,
45
+ calls `initializeDocumentation` with the demo save callbacks, wires
46
+ the F1 shortcut, and owns the `toggleHelp` / `showHelp` /
47
+ `showBook` / `showBookList` / `filterByGenre` navigation methods.
48
+ - `Pict-Application-Bookshop-Configuration.json` - the Pict
49
+ application stanza. Disables `AutoRenderMainViewportViewAfterInitialize`
50
+ so the application can do the initial render after the provider has
51
+ finished loading its topics.
52
+ - `data/BookshopData.json` - the books seed data
53
+ (`Title` / `Author` / `Price` / `Genre` / `Cover` / `Description` / `InStock`).
54
+ - `data/pict_documentation_topics.json` - the topics manifest. Five
55
+ topics, two of them carrying inline tooltip records that the
56
+ provider auto-registers.
57
+ - `docs/welcome.md`, `book-list.md`, `book-detail.md`, `store.md`,
58
+ `search-filter.md` - the topic content as plain markdown files. Each
59
+ referenced by `TopicHelpFilePath` in the manifest.
60
+ - `views/PictView-Bookshop-BookList.js` - grid of book cards with
61
+ genre filter. Sprinkles `data-d-tooltip="..."` and
62
+ `data-d-tooltip-icon` attributes everywhere; calls
63
+ `tmpDocProvider.scanTooltips()` after every render.
64
+ - `views/PictView-Bookshop-Store.js` - single-book store page. Same
65
+ pattern as BookList - `data-d-tooltip` attributes plus a `?` help
66
+ button that triggers `showHelp('BOOKSHOP-STORE')`.
67
+ - `views/PictView-Bookshop-HelpToggle.js` - a stub view for the
68
+ help-toggle slot, included for symmetry with future expansion.
69
+ - `html/index.html` - defines the layout shell: a header bar with the
70
+ help button, a content area, a slide-in help panel on the right, and
71
+ an F1 hint pinned to the bottom-right.
72
+
73
+ ## The data model
74
+
75
+ Two AppData roots, populated in `onAfterInitializeAsync`:
76
+
77
+ ```js
78
+ this.pict.AppData.Bookshop =
79
+ {
80
+ Books: _BookshopData.Books, // the catalog
81
+ CurrentBook: null, // selected book when in Store view
82
+ CurrentView: 'BookList', // 'BookList' or 'Store'
83
+ GenreFilter: '', // current genre filter, '' = show all
84
+ HelpVisible: false, // is the right-side panel open?
85
+ HelpTopicCode: 'BOOKSHOP-WELCOME' // initial topic
86
+ };
87
+
88
+ this.pict.AppData.InlineDocumentation = {};
89
+ this.pict.AppData.InlineDocumentation.Topics = _TopicsData;
90
+ ```
91
+
92
+ The provider keys its state under `AppData.InlineDocumentation.*`. Pre-loading
93
+ `Topics` before calling `initializeDocumentation` is a stash-then-init
94
+ pattern: the provider picks the topics up out of AppData rather than
95
+ needing them in the constructor, which makes hot-reload and
96
+ edit-mode-driven topic changes trivial.
97
+
98
+ The five-topic manifest (`pict_documentation_topics.json`) is the
99
+ canonical shape:
100
+
101
+ ```json
102
+ {
103
+ "BOOKSHOP-STORE": {
104
+ "TopicCode": "BOOKSHOP-STORE",
105
+ "TopicHelpFilePath": "store.md",
106
+ "TopicTitle": "The Store Page",
107
+ "Routes": ["/books/store/*"],
108
+ "Tooltips": {
109
+ "genre-badge": { "Content": "Books are categorized by genre to help you find similar titles.\n\nSee the [full catalog](help:book-list.md) to browse by genre." },
110
+ "store-price": { "Content": "The listed price is the final price in **USD**, tax included.\n\n[Pricing details](help:store.md#pricing)" }
111
+ // ... three more
112
+ }
113
+ },
114
+ // ... four more topics
115
+ }
116
+ ```
117
+
118
+ `Routes` is an array of route patterns the topic matches; `*` is a
119
+ wildcard. `Tooltips` is a `data-d-tooltip` -> `{Content}` map that the
120
+ provider auto-registers when the topic loads.
121
+
122
+ ---
123
+
124
+ ## Feature 1 - Initializing the documentation provider
125
+
126
+ The application's constructor adds the provider with its default
127
+ configuration. The actual initialisation happens in
128
+ `onAfterInitializeAsync`, where the demo wires three callbacks the
129
+ provider invokes:
130
+
131
+ ```js
132
+ let tmpDocProvider = this.pict.providers['Pict-InlineDocumentation'];
133
+ tmpDocProvider.initializeDocumentation(
134
+ {
135
+ DocsBaseURL: 'docs/',
136
+ onSave: (pSaveData, fSaveCallback) =>
137
+ {
138
+ // Demo save handler - log to console
139
+ this.log.info(`Bookshop: Saving document [${pSaveData.Path}] (${pSaveData.Content.length} chars)`);
140
+ // In a real app, this would PUT to an API
141
+ return fSaveCallback(null);
142
+ },
143
+ onTopicsSave: (pTopics, fSaveCallback) =>
144
+ {
145
+ // Demo topics save handler - log to console
146
+ this.log.info(`Bookshop: Saving topics (${Object.keys(pTopics).length} topics)`);
147
+ // In a real app, this would PUT to an API
148
+ return fSaveCallback(null);
149
+ },
150
+ onImageUpload: (pFile, pDocumentPath, fCallback) =>
151
+ {
152
+ // Demo image upload handler - log to console
153
+ this.log.info(`Bookshop: Image upload [${pFile.name}] (${pFile.size} bytes) for document [${pDocumentPath}]`);
154
+ // In a real app, this would POST the file to a server
155
+ return fCallback('Demo mode: no upload server configured');
156
+ }
157
+ },
158
+ () =>
159
+ {
160
+ // Enable edit mode for demo
161
+ tmpDocProvider.setEditEnabled(true);
162
+
163
+ // Render the main book list
164
+ this.pict.views['Bookshop-BookList'].render();
165
+
166
+ // Load help for the initial route
167
+ tmpDocProvider.navigateToRoute('/books');
168
+
169
+ // Set up F1 keyboard listener
170
+ this._setupKeyboardShortcuts();
171
+
172
+ return super.onAfterInitializeAsync(fCallback);
173
+ });
174
+ ```
175
+
176
+ Three things happen in the initialisation callback:
177
+
178
+ 1. **`setEditEnabled(true)`** - switches the help panel into edit mode,
179
+ so clicking the **Edit** button in the panel reveals a markdown
180
+ editor for the current topic. The `onSave` callback fires on Save;
181
+ the demo just logs to console.
182
+ 2. **`navigateToRoute('/books')`** - primes the help panel with the
183
+ topic that matches the initial route. This is how route-aware help
184
+ works: the application tells the provider what URL the user is on;
185
+ the provider looks up the matching topic from the manifest's
186
+ `Routes` arrays.
187
+ 3. **`_setupKeyboardShortcuts()`** - registers a `keydown` listener for
188
+ the F1 key. The legitimate exception to the no-`addEventListener`
189
+ rule: this is a window-level shortcut, not a per-view re-rendered
190
+ element.
191
+
192
+ The initial render only happens *inside* this callback because the
193
+ provider's initialisation is async (it loads markdown files via fetch
194
+ in real deployments). Rendering the BookList view earlier would risk
195
+ the help panel being empty when the user first sees the page.
196
+
197
+ ---
198
+
199
+ ## Feature 2 - Topics manifest with route mapping
200
+
201
+ The manifest is the single source of truth for *which topic shows
202
+ when*. Each topic has a `Routes` array:
203
+
204
+ ```json
205
+ "BOOKSHOP-WELCOME": {
206
+ "TopicCode": "BOOKSHOP-WELCOME",
207
+ "TopicHelpFilePath": "welcome.md",
208
+ "TopicTitle": "Welcome to the Bookshop",
209
+ "Routes": ["/"]
210
+ },
211
+ "BOOKSHOP-BOOKLIST": {
212
+ "TopicCode": "BOOKSHOP-BOOKLIST",
213
+ "TopicHelpFilePath": "book-list.md",
214
+ "TopicTitle": "Browsing the Book Catalog",
215
+ "Routes": ["/books", "/books/catalog"]
216
+ },
217
+ "BOOKSHOP-STORE": {
218
+ "TopicCode": "BOOKSHOP-STORE",
219
+ "TopicHelpFilePath": "store.md",
220
+ "TopicTitle": "The Store Page",
221
+ "Routes": ["/books/store/*"]
222
+ }
223
+ ```
224
+
225
+ `Routes: ["/"]` is the literal root; `["/books", "/books/catalog"]` is
226
+ two aliases for the same topic; `["/books/store/*"]` is a wildcard
227
+ that matches every store page regardless of the book id.
228
+
229
+ The application calls `navigateToRoute(...)` whenever it changes
230
+ "page":
231
+
232
+ ```js
233
+ showBook(pBookID)
234
+ {
235
+ let tmpState = this.pict.AppData.Bookshop;
236
+ let tmpDocProvider = this.pict.providers['Pict-InlineDocumentation'];
237
+
238
+ for (let i = 0; i < tmpState.Books.length; i++)
239
+ {
240
+ if (tmpState.Books[i].IDBook === pBookID)
241
+ {
242
+ tmpState.CurrentBook = tmpState.Books[i];
243
+ tmpState.CurrentView = 'Store';
244
+ this.pict.views['Bookshop-Store'].render();
245
+
246
+ // Update help for the store route
247
+ tmpDocProvider.navigateToRoute('/books/store/' + pBookID);
248
+ return;
249
+ }
250
+ }
251
+ }
252
+ ```
253
+
254
+ The route is fabricated - the bookshop is a single-page app with no
255
+ real URL changes. The route is just a string the application invents
256
+ to tell the documentation provider *what context* the user is in. In a
257
+ router-driven app the same call lives in the router's `onRouteChange`
258
+ callback.
259
+
260
+ ---
261
+
262
+ ## Feature 3 - `data-d-tooltip` attributes for inline tooltips
263
+
264
+ The BookList view sprinkles tooltip attributes through its HTML:
265
+
266
+ ```js
267
+ // Section header with help button
268
+ tmpHTML += '<div class="bookshop-section-header">';
269
+ tmpHTML += '<h2 class="bookshop-section-title" data-d-tooltip="catalog-title">Book Catalog</h2>';
270
+ tmpHTML += '<span data-d-tooltip="catalog-info" data-d-tooltip-icon></span>';
271
+ tmpHTML += '<button class="bookshop-help-btn" id="Bookshop-Help-BookList" title="Help: Book Catalog">?</button>';
272
+ tmpHTML += '</div>';
273
+
274
+ // Filter bar with help button
275
+ tmpHTML += '<div class="bookshop-filter-bar">';
276
+ tmpHTML += '<label data-d-tooltip="genre-filter">Genre:</label>';
277
+ // ...
278
+ ```
279
+
280
+ `data-d-tooltip="catalog-info"` is the tooltip key. The provider
281
+ matches it against the current topic's `Tooltips` block:
282
+
283
+ ```json
284
+ "Tooltips": {
285
+ "catalog-title": { "Content": "Browse our full collection of books. Click any book to view details and purchase.\n\nSee [Book Catalog Help](help:book-list.md) for the full guide." },
286
+ "catalog-info": { "Content": "**Tip:** Use the genre filter to narrow results, or click a book card to visit its store page.\n\nLearn more: [Searching & Filtering](help:search-filter.md)" },
287
+ "genre-filter": { "Content": "Filter the catalog by literary genre. Select **All Genres** to see everything.\n\nFor advanced filtering tips, see [Search & Filter Guide](help:search-filter.md)." }
288
+ }
289
+ ```
290
+
291
+ `data-d-tooltip-icon` (no value) tells the provider to **render a
292
+ small info icon into that span**. Elements without `data-d-tooltip-icon`
293
+ just gain hover behaviour on the existing content. The provider scans
294
+ on demand:
295
+
296
+ ```js
297
+ tmpContainer.innerHTML = tmpHTML;
298
+
299
+ // Wire click handlers
300
+ this._wireHandlers(tmpContainer);
301
+
302
+ // Scan for tooltip placeholders
303
+ let tmpDocProvider = this.pict.providers['Pict-InlineDocumentation'];
304
+ if (tmpDocProvider)
305
+ {
306
+ tmpDocProvider.scanTooltips();
307
+ }
308
+ ```
309
+
310
+ Calling `scanTooltips()` after every render is the contract - the
311
+ provider walks the DOM, finds every `data-d-tooltip` attribute, and
312
+ re-attaches its hover/focus tooltips. New elements added by a
313
+ re-render are picked up; orphaned references are cleaned up. The
314
+ provider also installs a MutationObserver as a fallback so
315
+ dynamically-injected nodes pick up tooltips even without an explicit
316
+ scan call.
317
+
318
+ ---
319
+
320
+ ## Feature 4 - Topic-keyed help buttons
321
+
322
+ The `?` button next to each section header bypasses route mapping and
323
+ jumps straight to a specific topic:
324
+
325
+ ```js
326
+ let tmpHelpBookList = pContainer.querySelector('#Bookshop-Help-BookList');
327
+ if (tmpHelpBookList)
328
+ {
329
+ tmpHelpBookList.addEventListener('click', (pEvent) =>
330
+ {
331
+ pEvent.stopPropagation();
332
+ if (tmpApp)
333
+ {
334
+ tmpApp.showHelp('BOOKSHOP-BOOKLIST');
335
+ }
336
+ });
337
+ }
338
+ ```
339
+
340
+ The application's `showHelp` opens the panel (if it isn't already
341
+ open) and calls the provider's `loadTopicDocument` with the topic
342
+ code:
343
+
344
+ ```js
345
+ showHelp(pTopicCode)
346
+ {
347
+ let tmpState = this.pict.AppData.Bookshop;
348
+ let tmpDocProvider = this.pict.providers['Pict-InlineDocumentation'];
349
+
350
+ tmpState.HelpTopicCode = pTopicCode;
351
+
352
+ // Ensure help panel is visible
353
+ if (!tmpState.HelpVisible)
354
+ {
355
+ tmpState.HelpVisible = true;
356
+ let tmpHelpPanel = document.getElementById('Bookshop-Help-Panel');
357
+ let tmpContentArea = document.getElementById('Bookshop-Content-Area');
358
+ if (tmpHelpPanel) { tmpHelpPanel.classList.add('visible'); }
359
+ if (tmpContentArea) { tmpContentArea.classList.add('help-open'); }
360
+ }
361
+
362
+ // Load the topic document
363
+ tmpDocProvider.loadTopicDocument(pTopicCode);
364
+ }
365
+ ```
366
+
367
+ `loadTopicDocument(pTopicCode)` is the provider's "show this exact
368
+ topic" API. It loads the markdown file declared in `TopicHelpFilePath`,
369
+ renders it into the help panel, and **registers any `Tooltips` block
370
+ attached to that topic** so they activate for the current view.
371
+
372
+ `navigateToRoute('...')` is the "show whatever matches this route"
373
+ API. Use it when the application changes context. `loadTopicDocument`
374
+ is for explicit user requests - clicking a `?` button, opening a
375
+ "how do I..." link, restoring a deep-linked help URL.
376
+
377
+ ---
378
+
379
+ ## Feature 5 - F1 keyboard shortcut
380
+
381
+ F1 is the universal "help me" key. The application registers it once
382
+ at boot:
383
+
384
+ ```js
385
+ _setupKeyboardShortcuts()
386
+ {
387
+ if (typeof document === 'undefined')
388
+ {
389
+ return;
390
+ }
391
+
392
+ document.addEventListener('keydown', (pEvent) =>
393
+ {
394
+ if (pEvent.key === 'F1')
395
+ {
396
+ pEvent.preventDefault();
397
+ this.toggleHelp();
398
+ }
399
+ });
400
+ }
401
+ ```
402
+
403
+ `toggleHelp()` flips `AppData.Bookshop.HelpVisible` and toggles the
404
+ `visible` class on the panel + the `help-open` class on the content
405
+ area:
406
+
407
+ ```js
408
+ toggleHelp()
409
+ {
410
+ let tmpState = this.pict.AppData.Bookshop;
411
+ tmpState.HelpVisible = !tmpState.HelpVisible;
412
+
413
+ let tmpHelpPanel = document.getElementById('Bookshop-Help-Panel');
414
+ let tmpContentArea = document.getElementById('Bookshop-Content-Area');
415
+
416
+ if (tmpState.HelpVisible)
417
+ {
418
+ if (tmpHelpPanel) { tmpHelpPanel.classList.add('visible'); }
419
+ if (tmpContentArea) { tmpContentArea.classList.add('help-open'); }
420
+ }
421
+ else
422
+ {
423
+ if (tmpHelpPanel) { tmpHelpPanel.classList.remove('visible'); }
424
+ if (tmpContentArea) { tmpContentArea.classList.remove('help-open'); }
425
+ }
426
+ }
427
+ ```
428
+
429
+ The slide-in animation lives in CSS (the panel's `transform: translateX(100%)`
430
+ toggles to `translateX(0)` on `.visible`); the content area's
431
+ `margin-right` animates from `0` to `380px` on `.help-open` to make
432
+ room.
433
+
434
+ Window-level keyboard handlers and `MutationObserver` callbacks are
435
+ the explicit exceptions to Pict's "no `addEventListener`" rule. There's
436
+ no inline-handler equivalent for keyboard shortcuts on the document,
437
+ and the listener is installed once at boot - it does not get torn
438
+ down by view re-renders.
439
+
440
+ ---
441
+
442
+ ## Feature 6 - Markdown topics with `help:` links
443
+
444
+ Each topic's markdown file is plain CommonMark, with one extension:
445
+ the `help:` URL scheme is intercepted by the provider so cross-topic
446
+ links don't navigate the browser.
447
+
448
+ ```markdown
449
+ # Browsing the Book Catalog
450
+
451
+ The catalog shows every book currently available, with cover, title,
452
+ author, and price. Click any card to open the [store page for that
453
+ book](help:store.md).
454
+
455
+ ## Filtering by Genre
456
+
457
+ The genre filter narrows the list. Pick **All Genres** to clear it.
458
+ For more advanced searches see the [Search & Filter guide](help:search-filter.md).
459
+ ```
460
+
461
+ `[Search & Filter guide](help:search-filter.md)` resolves to a click
462
+ that calls `loadTopicDocument(...)` for the topic whose
463
+ `TopicHelpFilePath` is `search-filter.md`. The browser stays on the
464
+ same page; the help panel swaps content.
465
+
466
+ Tooltip content uses the same syntax - every `Tooltips` entry's
467
+ `Content` field is markdown that may contain `help:` links, so a hover
468
+ tooltip can deep-link into a long-form topic.
469
+
470
+ The demo's `DocsBaseURL: 'docs/'` is the directory prefix the
471
+ provider prepends to every `TopicHelpFilePath`. In production this
472
+ typically points at a server endpoint or a CDN; locally it's just
473
+ the `docs/` folder copied next to the bundle.
474
+
475
+ ---
476
+
477
+ ## Feature 7 - Edit-mode demo with save callbacks
478
+
479
+ `setEditEnabled(true)` turns the help panel's content area into an
480
+ editable region. The user can edit a topic's markdown, click Save, and
481
+ the provider invokes the `onSave` callback the host supplied:
482
+
483
+ ```js
484
+ onSave: (pSaveData, fSaveCallback) =>
485
+ {
486
+ // Demo save handler - log to console
487
+ this.log.info(`Bookshop: Saving document [${pSaveData.Path}] (${pSaveData.Content.length} chars)`);
488
+ // In a real app, this would PUT to an API
489
+ return fSaveCallback(null);
490
+ }
491
+ ```
492
+
493
+ `pSaveData` is `{ Path, Content }`. The demo just logs and resolves
494
+ the callback with no error. In production the same callback typically
495
+ does:
496
+
497
+ ```js
498
+ return fetch('/api/docs/' + pSaveData.Path, { method: 'PUT', body: pSaveData.Content })
499
+ .then(() => fSaveCallback(null))
500
+ .catch((e) => fSaveCallback(e));
501
+ ```
502
+
503
+ `onTopicsSave` fires when the user edits the topics manifest itself
504
+ (via the topic-manager view); `onImageUpload` fires when a user drops
505
+ an image into the markdown editor. All three callbacks are async; the
506
+ provider waits for the host's `fSaveCallback(err)` before declaring
507
+ the save complete. The demo's `onImageUpload` deliberately errors out
508
+ with `'Demo mode: no upload server configured'` to show the panel's
509
+ inline error state.
510
+
511
+ ---
512
+
513
+ ## Running the example
514
+
515
+ ```bash
516
+ cd example_applications/bookshop
517
+ npm install
518
+ npm run build # quack build -> quack copy -> dist/
519
+ # Open dist/index.html in a browser, or serve dist/ statically:
520
+ # cd dist && python3 -m http.server 8080
521
+ # visit http://localhost:8080
522
+ ```
523
+
524
+ The build's `copyFiles` is what stages the markdown documents and the
525
+ topics manifest into `dist/docs/`, where the provider's
526
+ `DocsBaseURL: 'docs/'` finds them at runtime.
527
+
528
+ ## Things to try in the running app
529
+
530
+ - **Hover the `?` icons next to "Book Catalog" and "Genre"** - markdown
531
+ tooltips appear, with `help:` links you can click to jump to the
532
+ full topic.
533
+ - **Click the help button in the header** (top-right `?` Help) - the
534
+ panel slides in from the right. The content area shrinks to make
535
+ room.
536
+ - **Press F1** - same thing. F1 again to close.
537
+ - **Click any book card** - the store page loads, *and* the help
538
+ panel content swaps to the store topic because
539
+ `navigateToRoute('/books/store/' + id)` matched the wildcard route.
540
+ - **Click the `?` button next to a section header** -
541
+ `showHelp('BOOKSHOP-...')` opens the panel (if closed) and loads the
542
+ specific topic regardless of current route.
543
+ - **Hover the price** on the store page - a tooltip explains
544
+ pricing and links to the longer topic.
545
+ - **Click the Edit button in the help panel** - the content area
546
+ becomes editable. Make a change, click Save, watch the console log
547
+ the `onSave` callback firing with the document path and content
548
+ length.
549
+ - **Filter by Genre** - the catalog updates immediately. The help
550
+ panel doesn't change because the route is still `/books`.
551
+
552
+ ## Takeaways
553
+
554
+ 1. **The application doesn't render documentation.** It tells the
555
+ provider what route the user is on and what tooltip keys are
556
+ relevant; the provider does everything from markdown parsing to
557
+ hover/focus event wiring to error rendering.
558
+ 2. **The topics manifest is the contract.** Route mapping, tooltip
559
+ binding, and topic content all flow from the same JSON file. Adding
560
+ a new section is "add a topic with its `Routes` and `Tooltips`"; no
561
+ code change.
562
+ 3. **`scanTooltips()` after every render is the rule.** Re-renders
563
+ wipe the DOM; the provider needs to know to re-attach handlers.
564
+ Calling it is cheap (it's idempotent) and forgetting it manifests
565
+ as "tooltips work the first time then go silent after a filter."
566
+ 4. **`navigateToRoute` and `loadTopicDocument` are different verbs.**
567
+ Route changes follow user navigation; explicit help buttons override
568
+ the route for targeted jumps. Both end up calling the same provider
569
+ internals; the distinction is who's driving.
570
+ 5. **Save callbacks let the host own persistence.** The provider
571
+ reads, parses, edits, and re-renders markdown - but a host that
572
+ keeps its docs on a CMS, in a Git repo, or in S3 plugs into the
573
+ exact same callbacks. No subclassing required.
574
+
575
+ ## Related documentation
576
+
577
+ - [Overview](../../overview.md) - what inline documentation does and why
578
+ - [Quickstart](../../quickstart.md) - minimum-viable wiring
579
+ - [Embedding Level 3 - Hand-Authored Tooltips](../../embedding-level3-tooltips.md) - the `data-d-tooltip` pattern the bookshop uses
580
+ - [Embedding Level 4 - Auto-Generated Tooltips](../../embedding-level4-autogen.md) - the next step after this example
581
+ - [API Reference](../../api-reference.md) - every provider method (`initializeDocumentation`, `navigateToRoute`, `loadTopicDocument`, `scanTooltips`, `setEditEnabled`, ...)
582
+ - [Architecture](../../architecture.md) - how the provider, layout view, and topics manifest fit together