pict-section-inlinedocumentation 0.0.1

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 (51) hide show
  1. package/README.md +107 -0
  2. package/docs/.nojekyll +0 -0
  3. package/docs/README.md +83 -0
  4. package/docs/_cover.md +15 -0
  5. package/docs/_sidebar.md +24 -0
  6. package/docs/_topbar.md +8 -0
  7. package/docs/_version.json +7 -0
  8. package/docs/api-reference.md +185 -0
  9. package/docs/architecture.md +103 -0
  10. package/docs/css/docuserve.css +327 -0
  11. package/docs/embedding-level1-sidebar.md +92 -0
  12. package/docs/embedding-level2-routes.md +86 -0
  13. package/docs/embedding-level3-tooltips.md +97 -0
  14. package/docs/embedding-level4-autogen.md +126 -0
  15. package/docs/index.html +39 -0
  16. package/docs/overview.md +42 -0
  17. package/docs/quickstart.md +95 -0
  18. package/docs/reference.md +73 -0
  19. package/docs/retold-catalog.json +181 -0
  20. package/docs/retold-keyword-index.json +4374 -0
  21. package/example_applications/basic/docs/README.md +40 -0
  22. package/example_applications/basic/docs/_sidebar.md +4 -0
  23. package/example_applications/basic/docs/_topics.json +10 -0
  24. package/example_applications/basic/docs/advanced-topics.md +47 -0
  25. package/example_applications/basic/docs/getting-started.md +70 -0
  26. package/example_applications/basic/index.html +100 -0
  27. package/example_applications/bookshop/.quackage.json +10 -0
  28. package/example_applications/bookshop/Pict-Application-Bookshop-Configuration.json +15 -0
  29. package/example_applications/bookshop/Pict-Application-Bookshop.js +218 -0
  30. package/example_applications/bookshop/data/BookshopData.json +65 -0
  31. package/example_applications/bookshop/data/pict_documentation_topics.json +46 -0
  32. package/example_applications/bookshop/docs/_sidebar.md +6 -0
  33. package/example_applications/bookshop/docs/book-detail.md +21 -0
  34. package/example_applications/bookshop/docs/book-list.md +21 -0
  35. package/example_applications/bookshop/docs/search-filter.md +18 -0
  36. package/example_applications/bookshop/docs/store.md +29 -0
  37. package/example_applications/bookshop/docs/welcome.md +23 -0
  38. package/example_applications/bookshop/html/index.html +236 -0
  39. package/example_applications/bookshop/package.json +34 -0
  40. package/example_applications/bookshop/views/PictView-Bookshop-BookList.js +324 -0
  41. package/example_applications/bookshop/views/PictView-Bookshop-HelpToggle.js +44 -0
  42. package/example_applications/bookshop/views/PictView-Bookshop-Store.js +271 -0
  43. package/package.json +55 -0
  44. package/source/Pict-Section-InlineDocumentation.js +10 -0
  45. package/source/providers/Pict-Provider-InlineDocumentation.js +1995 -0
  46. package/source/views/Pict-View-InlineDocumentation-Content.js +542 -0
  47. package/source/views/Pict-View-InlineDocumentation-Layout.js +206 -0
  48. package/source/views/Pict-View-InlineDocumentation-Nav.js +475 -0
  49. package/source/views/Pict-View-InlineDocumentation-TopicManager.js +1623 -0
  50. package/test/Browser_Integration_tests.js +1449 -0
  51. package/test/Pict-Section-InlineDocumentation_test.js +1285 -0
@@ -0,0 +1,327 @@
1
+ /* ============================================================================
2
+ Pict Docuserve - Base Styles & Theme Variables
3
+ ============================================================================ */
4
+
5
+ /* ----------------------------------------------------------------------------
6
+ Theme variables — light defaults on :root.
7
+ Dark mode applies when either:
8
+ (a) the user explicitly selected dark via <html data-theme="dark">
9
+ (b) the user hasn't chosen anything AND the system prefers dark
10
+ An explicit <html data-theme="light"> pins the light palette regardless.
11
+ ---------------------------------------------------------------------------- */
12
+
13
+ :root
14
+ {
15
+ /* Surfaces */
16
+ --docuserve-bg: #FDFBF7;
17
+ --docuserve-bg-elevated: #FFFFFF;
18
+ --docuserve-border: #DDD6CA;
19
+ --docuserve-border-soft: #EAE3D8;
20
+
21
+ /* Text */
22
+ --docuserve-text: #2A241E;
23
+ --docuserve-text-strong: #3D3229;
24
+ --docuserve-text-muted: #5E5549;
25
+ --docuserve-text-dim: #8A7F72;
26
+
27
+ /* Accent / links */
28
+ --docuserve-accent: #2E7D74;
29
+ --docuserve-accent-hover: #236660;
30
+
31
+ /* Top bar */
32
+ --docuserve-topbar-bg: #3D3229;
33
+ --docuserve-topbar-text: #E8E0D4;
34
+ --docuserve-topbar-text-muted: #B5AA9A;
35
+ --docuserve-topbar-text-dim: #8A7F72;
36
+ --docuserve-topbar-hover-bg: #524438;
37
+ --docuserve-topbar-version-bg: rgba(255, 255, 255, 0.06);
38
+ --docuserve-topbar-version-border: rgba(255, 255, 255, 0.08);
39
+ --docuserve-topbar-version-text: #B5AA9A;
40
+
41
+ /* Sidebar */
42
+ --docuserve-sidebar-bg: #FAF7F1;
43
+ --docuserve-sidebar-border: #DDD6CA;
44
+ --docuserve-sidebar-border-soft: #E5DED1;
45
+ --docuserve-sidebar-text: #423D37;
46
+ --docuserve-sidebar-group-title: #3D3229;
47
+ --docuserve-sidebar-module-text: #5E5549;
48
+ --docuserve-sidebar-hover-bg: #EAE3D8;
49
+ --docuserve-sidebar-hover-text: #2E7D74;
50
+ --docuserve-sidebar-active-bg: #E5DED1;
51
+ --docuserve-sidebar-active-text: #2E7D74;
52
+ --docuserve-sidebar-search-bg: #FFFFFF;
53
+ --docuserve-sidebar-search-border: #DDD6CA;
54
+
55
+ /* Inline code */
56
+ --docuserve-inline-code-bg: #F0ECE4;
57
+ --docuserve-inline-code-text: #9E3A50;
58
+
59
+ /* Code block panel */
60
+ --docuserve-code-bg: #F6F3EE;
61
+ --docuserve-code-border: #E5DED1;
62
+ --docuserve-code-gutter-bg: #EFEAE0;
63
+ --docuserve-code-gutter-border: #DDD6CA;
64
+ --docuserve-code-gutter-text: #A59986;
65
+ --docuserve-code-text: #2A241E;
66
+
67
+ /* Syntax tokens — low-chroma dark-on-light palette */
68
+ --docuserve-tok-keyword: #A03472;
69
+ --docuserve-tok-string: #1A6640;
70
+ --docuserve-tok-number: #B25A00;
71
+ --docuserve-tok-comment: #8A7F72;
72
+ --docuserve-tok-operator: #2E7D74;
73
+ --docuserve-tok-punctuation: #2A241E;
74
+ --docuserve-tok-function: #2A5DB0;
75
+ --docuserve-tok-property: #9E3A50;
76
+ --docuserve-tok-tag: #9E3A50;
77
+ --docuserve-tok-attr-name: #B25A00;
78
+ --docuserve-tok-attr-value: #1A6640;
79
+
80
+ /* Tables, blockquotes, mermaid */
81
+ --docuserve-table-header-bg: #F5F0E8;
82
+ --docuserve-table-row-alt-bg: #F9F6F0;
83
+ --docuserve-blockquote-bg: #F7F5F0;
84
+ --docuserve-blockquote-border: #2E7D74;
85
+ --docuserve-blockquote-text: #5E5549;
86
+ --docuserve-mermaid-bg: #FFFFFF;
87
+
88
+ /* Scrollbars */
89
+ --docuserve-scrollbar-track: #F5F0E8;
90
+ --docuserve-scrollbar-thumb: #D4CCBE;
91
+ --docuserve-scrollbar-thumb-hover: #B5AA9A;
92
+ }
93
+
94
+ @media (prefers-color-scheme: dark)
95
+ {
96
+ :root:not([data-theme="light"])
97
+ {
98
+ --docuserve-bg: #15120F;
99
+ --docuserve-bg-elevated: #1B1814;
100
+ --docuserve-border: #2F2823;
101
+ --docuserve-border-soft: #26211C;
102
+
103
+ --docuserve-text: #E8E0D4;
104
+ --docuserve-text-strong: #F2ECE0;
105
+ --docuserve-text-muted: #B5AA9A;
106
+ --docuserve-text-dim: #7A6F62;
107
+
108
+ --docuserve-accent: #5DB8A8;
109
+ --docuserve-accent-hover: #7FCCB8;
110
+
111
+ --docuserve-topbar-bg: #1A1612;
112
+ --docuserve-topbar-text: #E8E0D4;
113
+ --docuserve-topbar-text-muted: #B5AA9A;
114
+ --docuserve-topbar-text-dim: #7A6F62;
115
+ --docuserve-topbar-hover-bg: #2A241E;
116
+ --docuserve-topbar-version-bg: rgba(255, 255, 255, 0.05);
117
+ --docuserve-topbar-version-border: rgba(255, 255, 255, 0.09);
118
+ --docuserve-topbar-version-text: #B5AA9A;
119
+
120
+ --docuserve-sidebar-bg: #1B1814;
121
+ --docuserve-sidebar-border: #2F2823;
122
+ --docuserve-sidebar-border-soft: #26211C;
123
+ --docuserve-sidebar-text: #C9C0B3;
124
+ --docuserve-sidebar-group-title: #F2ECE0;
125
+ --docuserve-sidebar-module-text: #B5AA9A;
126
+ --docuserve-sidebar-hover-bg: #2A241E;
127
+ --docuserve-sidebar-hover-text: #7FCCB8;
128
+ --docuserve-sidebar-active-bg: #2F2823;
129
+ --docuserve-sidebar-active-text: #7FCCB8;
130
+ --docuserve-sidebar-search-bg: #26211C;
131
+ --docuserve-sidebar-search-border: #2F2823;
132
+
133
+ --docuserve-inline-code-bg: #2A241E;
134
+ --docuserve-inline-code-text: #E8B07A;
135
+
136
+ --docuserve-code-bg: #1E1A17;
137
+ --docuserve-code-border: #2F2823;
138
+ --docuserve-code-gutter-bg: #17130F;
139
+ --docuserve-code-gutter-border: #2F2823;
140
+ --docuserve-code-gutter-text: #6A6052;
141
+ --docuserve-code-text: #E8E0D4;
142
+
143
+ --docuserve-tok-keyword: #C678DD;
144
+ --docuserve-tok-string: #98C379;
145
+ --docuserve-tok-number: #D19A66;
146
+ --docuserve-tok-comment: #7F848E;
147
+ --docuserve-tok-operator: #56B6C2;
148
+ --docuserve-tok-punctuation: #E8E0D4;
149
+ --docuserve-tok-function: #61AFEF;
150
+ --docuserve-tok-property: #E06C75;
151
+ --docuserve-tok-tag: #E06C75;
152
+ --docuserve-tok-attr-name: #D19A66;
153
+ --docuserve-tok-attr-value: #98C379;
154
+
155
+ --docuserve-table-header-bg: #26211C;
156
+ --docuserve-table-row-alt-bg: #1F1B17;
157
+ --docuserve-blockquote-bg: #1F1B17;
158
+ --docuserve-blockquote-border: #5DB8A8;
159
+ --docuserve-blockquote-text: #C9C0B3;
160
+ --docuserve-mermaid-bg: #E8E0D4;
161
+
162
+ --docuserve-scrollbar-track: #1B1814;
163
+ --docuserve-scrollbar-thumb: #3A322B;
164
+ --docuserve-scrollbar-thumb-hover: #524438;
165
+ }
166
+ }
167
+
168
+ :root[data-theme="dark"]
169
+ {
170
+ --docuserve-bg: #15120F;
171
+ --docuserve-bg-elevated: #1B1814;
172
+ --docuserve-border: #2F2823;
173
+ --docuserve-border-soft: #26211C;
174
+
175
+ --docuserve-text: #E8E0D4;
176
+ --docuserve-text-strong: #F2ECE0;
177
+ --docuserve-text-muted: #B5AA9A;
178
+ --docuserve-text-dim: #7A6F62;
179
+
180
+ --docuserve-accent: #5DB8A8;
181
+ --docuserve-accent-hover: #7FCCB8;
182
+
183
+ --docuserve-topbar-bg: #1A1612;
184
+ --docuserve-topbar-text: #E8E0D4;
185
+ --docuserve-topbar-text-muted: #B5AA9A;
186
+ --docuserve-topbar-text-dim: #7A6F62;
187
+ --docuserve-topbar-hover-bg: #2A241E;
188
+ --docuserve-topbar-version-bg: rgba(255, 255, 255, 0.05);
189
+ --docuserve-topbar-version-border: rgba(255, 255, 255, 0.09);
190
+ --docuserve-topbar-version-text: #B5AA9A;
191
+
192
+ --docuserve-sidebar-bg: #1B1814;
193
+ --docuserve-sidebar-border: #2F2823;
194
+ --docuserve-sidebar-border-soft: #26211C;
195
+ --docuserve-sidebar-text: #C9C0B3;
196
+ --docuserve-sidebar-group-title: #F2ECE0;
197
+ --docuserve-sidebar-module-text: #B5AA9A;
198
+ --docuserve-sidebar-hover-bg: #2A241E;
199
+ --docuserve-sidebar-hover-text: #7FCCB8;
200
+ --docuserve-sidebar-active-bg: #2F2823;
201
+ --docuserve-sidebar-active-text: #7FCCB8;
202
+ --docuserve-sidebar-search-bg: #26211C;
203
+ --docuserve-sidebar-search-border: #2F2823;
204
+
205
+ --docuserve-inline-code-bg: #2A241E;
206
+ --docuserve-inline-code-text: #E8B07A;
207
+
208
+ --docuserve-code-bg: #1E1A17;
209
+ --docuserve-code-border: #2F2823;
210
+ --docuserve-code-gutter-bg: #17130F;
211
+ --docuserve-code-gutter-border: #2F2823;
212
+ --docuserve-code-gutter-text: #6A6052;
213
+ --docuserve-code-text: #E8E0D4;
214
+
215
+ --docuserve-tok-keyword: #C678DD;
216
+ --docuserve-tok-string: #98C379;
217
+ --docuserve-tok-number: #D19A66;
218
+ --docuserve-tok-comment: #7F848E;
219
+ --docuserve-tok-operator: #56B6C2;
220
+ --docuserve-tok-punctuation: #E8E0D4;
221
+ --docuserve-tok-function: #61AFEF;
222
+ --docuserve-tok-property: #E06C75;
223
+ --docuserve-tok-tag: #E06C75;
224
+ --docuserve-tok-attr-name: #D19A66;
225
+ --docuserve-tok-attr-value: #98C379;
226
+
227
+ --docuserve-table-header-bg: #26211C;
228
+ --docuserve-table-row-alt-bg: #1F1B17;
229
+ --docuserve-blockquote-bg: #1F1B17;
230
+ --docuserve-blockquote-border: #5DB8A8;
231
+ --docuserve-blockquote-text: #C9C0B3;
232
+ --docuserve-mermaid-bg: #E8E0D4;
233
+
234
+ --docuserve-scrollbar-track: #1B1814;
235
+ --docuserve-scrollbar-thumb: #3A322B;
236
+ --docuserve-scrollbar-thumb-hover: #524438;
237
+ }
238
+
239
+ /* ----------------------------------------------------------------------------
240
+ Reset and base
241
+ ---------------------------------------------------------------------------- */
242
+
243
+ *, *::before, *::after
244
+ {
245
+ box-sizing: border-box;
246
+ }
247
+
248
+ html, body
249
+ {
250
+ margin: 0;
251
+ padding: 0;
252
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
253
+ font-size: 16px;
254
+ line-height: 1.5;
255
+ color: var(--docuserve-text);
256
+ background-color: var(--docuserve-bg);
257
+ -webkit-font-smoothing: antialiased;
258
+ -moz-osx-font-smoothing: grayscale;
259
+ transition: background-color 0.15s ease, color 0.15s ease;
260
+ }
261
+
262
+ /* Typography */
263
+ h1, h2, h3, h4, h5, h6
264
+ {
265
+ margin-top: 0;
266
+ line-height: 1.3;
267
+ color: var(--docuserve-text-strong);
268
+ }
269
+
270
+ a
271
+ {
272
+ color: var(--docuserve-accent);
273
+ text-decoration: none;
274
+ }
275
+
276
+ a:hover
277
+ {
278
+ color: var(--docuserve-accent-hover);
279
+ }
280
+
281
+ /* Application container */
282
+ #Docuserve-Application-Container
283
+ {
284
+ min-height: 100vh;
285
+ }
286
+
287
+ /* Utility: scrollbar styling */
288
+ ::-webkit-scrollbar
289
+ {
290
+ width: 8px;
291
+ height: 8px;
292
+ }
293
+
294
+ ::-webkit-scrollbar-track
295
+ {
296
+ background: var(--docuserve-scrollbar-track);
297
+ }
298
+
299
+ ::-webkit-scrollbar-thumb
300
+ {
301
+ background: var(--docuserve-scrollbar-thumb);
302
+ border-radius: 4px;
303
+ }
304
+
305
+ ::-webkit-scrollbar-thumb:hover
306
+ {
307
+ background: var(--docuserve-scrollbar-thumb-hover);
308
+ }
309
+
310
+ /* Responsive adjustments */
311
+ @media (max-width: 768px)
312
+ {
313
+ html
314
+ {
315
+ font-size: 14px;
316
+ }
317
+
318
+ #Docuserve-Sidebar-Container
319
+ {
320
+ display: none;
321
+ }
322
+
323
+ .docuserve-body
324
+ {
325
+ flex-direction: column;
326
+ }
327
+ }
@@ -0,0 +1,92 @@
1
+ # Level 1 -- Sidebar + Table of Contents
2
+
3
+ The lightest touch. You drop in a sidebar that lists every topic in your docs corpus and lets the user click through. No route mapping, no tooltips, no manifest wiring -- just a reading surface.
4
+
5
+ Good for: internal tools, admin panels, anything where "there's a help pane in the corner" is enough.
6
+
7
+ ## What You End Up With
8
+
9
+ - A collapsible sidebar pane (or drawer) with a tree of topics grouped by category
10
+ - A reading surface that renders the selected topic as HTML
11
+ - Keyword search across the corpus (if a keyword index is present)
12
+
13
+ ## HTML
14
+
15
+ Add a single container element wherever you want the help panel to live:
16
+
17
+ ```html
18
+ <aside id="AppHelpSidebar" class="app-help"></aside>
19
+ ```
20
+
21
+ ## JavaScript
22
+
23
+ ```js
24
+ const libPict = require('pict');
25
+ const libInlineDocs = require('pict-section-inlinedocumentation');
26
+
27
+ const _Pict = new libPict({ Product: 'My App', Version: '1.0.0' });
28
+
29
+ _Pict.addSection('InlineDocumentation', libInlineDocs,
30
+ {
31
+ DocumentationRoot: '/docs/',
32
+ CatalogURL: '/docs/retold-catalog.json',
33
+ KeywordIndexURL: '/docs/retold-keyword-index.json',
34
+ DefaultTopic: 'overview',
35
+ SidebarContainer: '#AppHelpSidebar'
36
+ });
37
+
38
+ _Pict.onAfterInitializeAsync = async () =>
39
+ {
40
+ await _Pict.views.InlineDocumentation.renderAsync();
41
+ };
42
+ ```
43
+
44
+ That's it. The section owns the sidebar, owns the reading pane (it creates an internal one if `ReadingPaneContainer` is not provided), and owns the click handlers.
45
+
46
+ ## Toggling Visibility
47
+
48
+ Most apps hide help behind a button. Bind it to a provider flag:
49
+
50
+ ```js
51
+ document.getElementById('HelpToggleButton').addEventListener('click', () =>
52
+ {
53
+ const tmpPane = document.getElementById('AppHelpSidebar');
54
+ tmpPane.classList.toggle('open');
55
+ });
56
+ ```
57
+
58
+ The section doesn't care whether its container is visible -- it only renders when asked.
59
+
60
+ ## Grouping and Ordering
61
+
62
+ Topic grouping is taken from the catalog's `Category` field, which `pict-docuserve prepare-docs` populates from frontmatter. Add frontmatter to your Markdown:
63
+
64
+ ```markdown
65
+ ---
66
+ title: Editing Records
67
+ category: Records
68
+ order: 20
69
+ ---
70
+
71
+ # Editing Records
72
+
73
+ ...
74
+ ```
75
+
76
+ Topics are sorted by `order` within each category, then by title.
77
+
78
+ ## Search
79
+
80
+ If you pointed at a `retold-keyword-index.json`, the sidebar automatically gets a search box at the top. To drive search from your own UI instead:
81
+
82
+ ```js
83
+ document.getElementById('MyHelpSearch').addEventListener('input', async (e) =>
84
+ {
85
+ const tmpResults = await _Pict.views.InlineDocumentation.searchAsync(e.target.value);
86
+ renderResults(tmpResults);
87
+ });
88
+ ```
89
+
90
+ ## When to Graduate
91
+
92
+ If users are having to hunt for the right topic, it's time to jump to **[Level 2: Route-Mapped Content](#/page/embedding-level2-routes.md)** so the help pane follows them automatically.
@@ -0,0 +1,86 @@
1
+ # Level 2 -- Route-Mapped Content
2
+
3
+ Now the help pane knows where the user is. As they navigate, the reading surface automatically switches to the topic that describes the screen they're looking at. The user never has to search.
4
+
5
+ Good for: wizard-style apps, CRUD panels, anything with a clear screen-to-concept mapping.
6
+
7
+ ## Prerequisites
8
+
9
+ - Level 1 is already working.
10
+ - Your app uses [`pict-router`](/pict/pict-router/) (or exposes a comparable hash or history router).
11
+
12
+ ## Declare the Map
13
+
14
+ Route patterns use the same syntax as `pict-router`: colon-prefixed tokens for named params, `*` for a catch-all. The first match wins, so order matters.
15
+
16
+ ```js
17
+ _Pict.addSection('InlineDocumentation', libInlineDocs,
18
+ {
19
+ DocumentationRoot: '/docs/',
20
+ CatalogURL: '/docs/retold-catalog.json',
21
+ SidebarContainer: '#AppHelpSidebar',
22
+ DefaultTopic: 'overview',
23
+ RouteMap:
24
+ [
25
+ // Exact screens first
26
+ { Pattern: '/records/:entity/new', Topic: 'records/creating' },
27
+ { Pattern: '/records/:entity/:id/edit', Topic: 'records/editing' },
28
+ { Pattern: '/records/:entity/:id', Topic: 'records/viewing' },
29
+
30
+ // Browse
31
+ { Pattern: '/records/:entity', Topic: 'records/browsing' },
32
+
33
+ // Settings has many children that all share one topic
34
+ { Pattern: '/settings/*', Topic: 'settings' },
35
+
36
+ // Top level
37
+ { Pattern: '/', Topic: 'home' }
38
+ ]
39
+ });
40
+ ```
41
+
42
+ ## Attach the Router
43
+
44
+ If your app registers the router as a provider, the section can pick it up:
45
+
46
+ ```js
47
+ _Pict.onAfterInitializeAsync = async () =>
48
+ {
49
+ _Pict.views.InlineDocumentation.attachRouter(_Pict.providers.Router);
50
+ await _Pict.views.InlineDocumentation.renderAsync();
51
+ };
52
+ ```
53
+
54
+ `attachRouter` subscribes to the router's change event and re-evaluates the route map on every navigation.
55
+
56
+ ## Dynamic Topics Per Entity
57
+
58
+ A common trick: use the route params to pick a more specific topic.
59
+
60
+ ```js
61
+ _Pict.views.InlineDocumentation.setRouteMap(
62
+ [
63
+ {
64
+ Pattern: '/records/:entity/:id/edit',
65
+ Topic: (pParams) => `records/${pParams.entity}/editing`,
66
+ Fallback: 'records/editing'
67
+ }
68
+ ]);
69
+ ```
70
+
71
+ If the per-entity topic exists it is used; if not, the `Fallback` kicks in. This lets you incrementally add entity-specific copy without a big-bang rewrite.
72
+
73
+ ## Manually Overriding
74
+
75
+ Sometimes a screen has multiple conceptual modes and the router can't see the difference (e.g., a tabbed view). Call `loadTopicAsync` directly when the user switches tab:
76
+
77
+ ```js
78
+ customerTabs.on('change', async (pTabId) =>
79
+ {
80
+ await _Pict.views.InlineDocumentation.loadTopicAsync(`customers/tabs/${pTabId}`);
81
+ });
82
+ ```
83
+
84
+ ## When to Graduate
85
+
86
+ Screen-level help is good for orientation but useless when the user is staring at a specific field and wondering "what goes here?". That is the job of **[Level 3: Hand-Authored Tooltips](#/page/embedding-level3-tooltips.md)**.
@@ -0,0 +1,97 @@
1
+ # Level 3 -- Hand-Authored Tooltips on Specific Features
2
+
3
+ Now the help comes to the user. Individual fields, buttons, column headers, and icons get their own tooltip, each backed by a named topic. You write each topic by hand -- so the content is as rich as you like, with Markdown, lists, even Mermaid diagrams.
4
+
5
+ Good for: forms with non-obvious fields, dashboards with domain-specific metrics, anywhere power users live.
6
+
7
+ ## Mark Up the Controls
8
+
9
+ Add `data-help="<topic-key>"` to any element that should get a tooltip:
10
+
11
+ ```html
12
+ <form id="CustomerForm">
13
+ <label>
14
+ Email
15
+ <input name="email" data-help="customers/email-field" />
16
+ </label>
17
+
18
+ <label>
19
+ Tax ID
20
+ <input name="taxId" data-help="customers/tax-id-field" />
21
+ </label>
22
+
23
+ <button type="submit" data-help="customers/save-button">Save</button>
24
+ </form>
25
+ ```
26
+
27
+ ## Bind Them
28
+
29
+ After the form has rendered:
30
+
31
+ ```js
32
+ await _Pict.views.InlineDocumentation.bindTooltipsAsync('#CustomerForm');
33
+ ```
34
+
35
+ Every matching element gets hover- and focus-triggered tooltips. New elements added to the form later are picked up automatically by the section's MutationObserver.
36
+
37
+ ## Write the Topic
38
+
39
+ Each topic is a plain Markdown file under your docs root:
40
+
41
+ ```markdown
42
+ ---
43
+ title: Customer Email
44
+ category: Customers
45
+ ---
46
+
47
+ # Customer Email
48
+
49
+ The primary email address for this customer. Used for:
50
+
51
+ - Invoice delivery
52
+ - Shipment notifications
53
+ - Password resets
54
+
55
+ **Format:** Standard RFC 5322. International addresses are supported.
56
+
57
+ > Note: changing this address does **not** retroactively update sent invoices.
58
+ ```
59
+
60
+ Tooltips render the same Markdown you'd get in the sidebar, trimmed to a sensible size. If a topic has a frontmatter `short` field, that is used in the tooltip; the full body is shown when the user clicks "More" (which links into the sidebar at that topic).
61
+
62
+ ```markdown
63
+ ---
64
+ title: Tax ID
65
+ short: Must be a valid VAT, EIN, or local tax identifier. Format is validated on save.
66
+ ---
67
+
68
+ # Tax ID
69
+
70
+ The tax identifier your jurisdiction requires on invoices...
71
+ ```
72
+
73
+ ## Showing a Tooltip Programmatically
74
+
75
+ On validation failure it is often useful to force the tooltip open at the right topic:
76
+
77
+ ```js
78
+ form.on('validation-error', (pEvent) =>
79
+ {
80
+ const tmpField = form.fieldElement(pEvent.field);
81
+ _Pict.views.InlineDocumentation.showTooltip(tmpField, `customers/${pEvent.field}-invalid`);
82
+ });
83
+ ```
84
+
85
+ Call `hideTooltip()` when the error is cleared.
86
+
87
+ ## Cleanup
88
+
89
+ When tearing down a view, unbind to avoid leaking listeners:
90
+
91
+ ```js
92
+ _Pict.views.InlineDocumentation.unbindTooltips('#CustomerForm');
93
+ ```
94
+
95
+ ## When to Graduate
96
+
97
+ Writing `data-help` on every control, and a Markdown file for every `data-help` key, gets old fast. If your form is already described by a Manyfest, **[Level 4: Auto-Generated Tooltips](#/page/embedding-level4-autogen.md)** can do all of this for you.