markdowndocs 0.6.1 → 0.8.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +88 -0
- data/README.md +95 -14
- data/app/controllers/markdowndocs/docs_controller.rb +29 -9
- data/app/controllers/markdowndocs/preferences_controller.rb +62 -3
- data/app/helpers/markdowndocs/docs_helper.rb +6 -2
- data/app/models/markdowndocs/documentation.rb +136 -20
- data/app/services/markdowndocs/markdown_renderer.rb +50 -19
- data/app/views/markdowndocs/docs/_mode_switcher.html.erb +2 -1
- data/app/views/markdowndocs/docs/show.html.erb +6 -1
- data/config/routes.rb +11 -0
- data/docs/superpowers/plans/2026-05-15-path-based-audience-routing.md +1619 -0
- data/docs/superpowers/specs/2026-05-15-path-based-audience-routing-design.md +311 -0
- data/lib/markdowndocs/configuration.rb +9 -1
- data/lib/markdowndocs/version.rb +1 -1
- data/lib/markdowndocs.rb +7 -0
- metadata +3 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# Path-Based Audience Routing — Design Spec
|
|
2
|
+
|
|
3
|
+
- **Date:** 2026-05-15
|
|
4
|
+
- **Status:** Approved for planning
|
|
5
|
+
- **Target version:** v0.7.0
|
|
6
|
+
- **Driver:** Editor experience — make documentation content easily updatable by different groups of editors using only filesystem conventions, without requiring frontmatter literacy.
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
The markdowndocs gem currently supports audience-specific content through three mechanisms layered on a single concept of "current mode" (one of the strings in `Markdowndocs.config.modes`, default `%w[guide technical]`):
|
|
11
|
+
|
|
12
|
+
| Layer | Where | Reference |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| Config universe | `config.modes` | [configuration.rb:13](../../../lib/markdowndocs/configuration.rb#L13) |
|
|
15
|
+
| Whole-doc filter | `audience:` YAML frontmatter (v0.6.0, 2026-05-13) | [docs_controller.rb:15](../../../app/controllers/markdowndocs/docs_controller.rb#L15) |
|
|
16
|
+
| In-doc fragment filter | `<!-- mode: X -->...<!-- /mode -->` HTML comments | [markdown_renderer.rb:29-45](../../../app/services/markdowndocs/markdown_renderer.rb#L29-L45) |
|
|
17
|
+
|
|
18
|
+
The current per-request mode is resolved in [docs_controller.rb:90-98](../../../app/controllers/markdowndocs/docs_controller.rb#L90-L98) from `params[:mode]`, then `Markdowndocs.config.user_mode_resolver`, then a cookie, then `config.default_mode`.
|
|
19
|
+
|
|
20
|
+
There is no authorization layer — any visitor can append `?mode=technical` and view audience-restricted content. The `user_mode_resolver` lambda only resolves a user's *preferred* mode, not a *permitted* set.
|
|
21
|
+
|
|
22
|
+
## Problem
|
|
23
|
+
|
|
24
|
+
Two related issues motivate this design:
|
|
25
|
+
|
|
26
|
+
1. **Authoring ergonomics.** `audience:` frontmatter requires editors to learn YAML conventions. Non-technical contributors making a content edit cannot easily tell, just by browsing the repo, which docs belong to which audience. Documents from different audiences are commingled in one directory.
|
|
27
|
+
2. **Editor isolation.** Different teams should be able to own different slices of the documentation (e.g., engineering owns technical docs, customer success owns guide docs). Today there is no filesystem signal that supports this — CODEOWNERS, branch protection, IDE folder views, and OS permissions all key off paths, but `audience:` lives inside the files.
|
|
28
|
+
|
|
29
|
+
This spec **does not** address authorization (gating viewer access by role/group). That concern was explicitly scoped out of the gem during brainstorming; host apps will continue to gate access externally (e.g., by wrapping `mount Markdowndocs::Engine` in an `authenticate` block).
|
|
30
|
+
|
|
31
|
+
## Design Decisions
|
|
32
|
+
|
|
33
|
+
### D1. Subdirectory-named modes drive whole-doc audience scoping
|
|
34
|
+
|
|
35
|
+
Files under `app/docs/` are scoped to audiences by their location:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
app/docs/
|
|
39
|
+
├── getting_started.md → shared (visible in every mode)
|
|
40
|
+
├── billing.md → shared
|
|
41
|
+
└── technical/
|
|
42
|
+
├── architecture.md → technical mode only
|
|
43
|
+
└── billing.md → technical mode only
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Rule:** A first-level subdirectory of the docs root whose name exactly matches an entry in `Markdowndocs.config.modes` is an *audience scope*. Files inside that subdirectory are visible only when the current mode equals the subdirectory name.
|
|
47
|
+
|
|
48
|
+
**Files at the docs root are *shared* — visible in every mode.**
|
|
49
|
+
|
|
50
|
+
**Non-mode subdirectories.** A first-level subdirectory whose name does *not* match any entry in `config.modes` is ignored by the document discovery walker. The gem logs a warning once per boot for each such subdirectory:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
[Markdowndocs] Ignoring subdirectory app/docs/api/ — name does not match any
|
|
54
|
+
configured mode (config.modes = ["guide", "technical"]). Files inside this
|
|
55
|
+
subdirectory will not be discovered. Move them into app/docs/ or into a
|
|
56
|
+
mode-named subdirectory.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Nested subdirectories within a mode scope (`app/docs/technical/sub/foo.md`) are also out of scope for this design; see "Out of Scope" below.
|
|
60
|
+
|
|
61
|
+
**Empty mode subdirectories.** `config.modes` may declare modes that have no corresponding subdirectory yet (e.g., a host is planning to add `sre` content). The mode remains valid for preference, the switcher offers it, and its index shows only shared docs. This is not an error.
|
|
62
|
+
|
|
63
|
+
Alternatives considered:
|
|
64
|
+
- *Filename-suffix variant* (`getting_started_technical.md`): rejected. CODEOWNERS patterns compose poorly with suffixes, mode renames touch every file, and the convention scales worse to N modes.
|
|
65
|
+
- *Default-mode-only at root* (root = only the default mode, every audience is a silo): rejected. Less natural for shared "About us" or "Getting started" docs that all audiences should read.
|
|
66
|
+
|
|
67
|
+
### D2. Distinct URLs (RESTful)
|
|
68
|
+
|
|
69
|
+
Each markdown file maps to exactly one URL. URLs do not change content based on mode.
|
|
70
|
+
|
|
71
|
+
| File | URL |
|
|
72
|
+
|---|---|
|
|
73
|
+
| `app/docs/getting_started.md` | `/docs/getting_started` |
|
|
74
|
+
| `app/docs/technical/architecture.md` | `/docs/technical/architecture` |
|
|
75
|
+
| `app/docs/technical/getting_started.md` | `/docs/technical/getting_started` |
|
|
76
|
+
|
|
77
|
+
This means it is valid for both `app/docs/getting_started.md` and `app/docs/technical/getting_started.md` to exist simultaneously — they are two distinct documents at two distinct URLs, sharing the slug `getting_started` within different mode locations.
|
|
78
|
+
|
|
79
|
+
**Rationale:** Shared links must point to a specific document. A reader copying `/docs/getting_started` from their address bar and pasting it into Slack expects every recipient to see the same content, regardless of which mode the recipient prefers.
|
|
80
|
+
|
|
81
|
+
### D3. Mode is a discovery preference, not an access gate
|
|
82
|
+
|
|
83
|
+
The gem does not authorize URL access by mode. A user in guide mode whose browser hits `/docs/technical/architecture` receives the document. The mode setting only affects:
|
|
84
|
+
|
|
85
|
+
- Which docs appear in the index
|
|
86
|
+
- Which docs appear in the sidebar / related-docs list
|
|
87
|
+
- Which docs are returned by search (when `config.search_enabled` is true)
|
|
88
|
+
- Mode switcher behavior (see D6)
|
|
89
|
+
|
|
90
|
+
Host apps that wish to restrict access to a subtree wrap the engine mount in their existing auth system. Examples:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
# config/routes.rb in the host app
|
|
94
|
+
authenticate :user, ->(u) { u.staff? } do
|
|
95
|
+
mount Markdowndocs::Engine, at: "/docs"
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
…or place a parent controller in front of the engine, or use route constraints. None of this is the gem's responsibility.
|
|
100
|
+
|
|
101
|
+
### D4. Index page composition: merged into configured categories
|
|
102
|
+
|
|
103
|
+
Per mode, the index shows the union of shared docs and that mode's docs, slotted into the categories declared in `Markdowndocs.config.categories`. There is no separate "Shared" section, no audience badge, and no auto-generated mode-named category.
|
|
104
|
+
|
|
105
|
+
Categories whose visible-doc set is empty under the current mode are dropped from the index (matches the v0.6.0 behavior for `audience:` frontmatter).
|
|
106
|
+
|
|
107
|
+
### D5. `config.categories` slug format
|
|
108
|
+
|
|
109
|
+
`config.categories` continues to accept a hash of `String category_name => Array<String> slugs`. The slug entries gain a path-prefix convention:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
config.categories = {
|
|
113
|
+
"Getting Started" => %w[welcome quickstart],
|
|
114
|
+
"Architecture" => %w[technical/architecture technical/data_model],
|
|
115
|
+
"Billing" => %w[billing technical/billing]
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
| Slug entry | Matches file | Visible in |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `welcome` | `app/docs/welcome.md` | Every mode |
|
|
122
|
+
| `technical/architecture` | `app/docs/technical/architecture.md` | Technical mode only |
|
|
123
|
+
| `billing` | `app/docs/billing.md` | Every mode |
|
|
124
|
+
| `technical/billing` | `app/docs/technical/billing.md` | Technical mode only |
|
|
125
|
+
|
|
126
|
+
A bare slug (no slash) matches a file at the docs root. A path-prefixed slug (one slash) matches a file in a mode subdirectory. The first segment of a path-prefixed slug must equal an entry in `config.modes`.
|
|
127
|
+
|
|
128
|
+
### D6. Mode switcher behavior: smart navigation
|
|
129
|
+
|
|
130
|
+
When the user toggles the mode switcher, the engine attempts to navigate to a same-slug document in the target mode's location. The slug used for comparison is the basename of the current doc's source path (file name without `.md`).
|
|
131
|
+
|
|
132
|
+
**Unified lookup rule.** Given target mode `M`, current slug `S`, and current URL `U`:
|
|
133
|
+
|
|
134
|
+
1. If `app/docs/M/S.md` exists AND its URL is not equal to `U` → redirect to its URL.
|
|
135
|
+
2. Else if `app/docs/S.md` exists AND its URL is not equal to `U` → redirect to its URL.
|
|
136
|
+
3. Else → stay on `U`.
|
|
137
|
+
|
|
138
|
+
In all three branches, persist the new preference (cookie + `user_mode_saver` if configured). The "not equal to `U`" guard prevents pointless self-redirects.
|
|
139
|
+
|
|
140
|
+
Applied to each case:
|
|
141
|
+
|
|
142
|
+
| Currently viewing | Toggle to | Lookup | Outcome |
|
|
143
|
+
|---|---|---|---|
|
|
144
|
+
| `/docs/billing` | technical | `app/docs/technical/billing.md`? else `app/docs/billing.md` (= U, skip) | Redirect to `/docs/technical/billing` if it exists; else stay |
|
|
145
|
+
| `/docs/technical/billing` | guide | `app/docs/guide/billing.md`? else `app/docs/billing.md` | Redirect to the guide variant if it exists; else redirect to shared if it exists; else stay |
|
|
146
|
+
| `/docs/technical/architecture` (no shared sibling) | guide | `app/docs/guide/architecture.md`? else `app/docs/architecture.md` (neither exists) | Stay on `/docs/technical/architecture` |
|
|
147
|
+
| `/docs` (index, no slug) | technical | n/a — no slug to look up | Stay on `/docs`. Index re-renders with technical-mode content |
|
|
148
|
+
|
|
149
|
+
The lookup is direction-agnostic: the same rule handles shared→mode, mode→shared (implicit), and mode→other-mode transitions.
|
|
150
|
+
|
|
151
|
+
### D7. `audience:` frontmatter is deprecated
|
|
152
|
+
|
|
153
|
+
Authors using `audience:` in front matter receive a deprecation warning at request time (logged once per file path per process boot) suggesting the file-move migration. The key continues to function in v0.7.x exactly as it does in v0.6.x — no behavior change for hosts that have already adopted it.
|
|
154
|
+
|
|
155
|
+
**Warning text:**
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
[Markdowndocs] DEPRECATION: `audience:` frontmatter in app/docs/foo.md is
|
|
159
|
+
deprecated. Move the file to app/docs/technical/foo.md instead and remove
|
|
160
|
+
the `audience:` key. The `audience:` key will be removed in v1.0.0.
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
For multi-audience frontmatter (`audience: [guide, technical]`), the suggested target is the file root (no move) and dropping the key. The warning emitter substitutes the right suggestion based on the resolved audience array.
|
|
164
|
+
|
|
165
|
+
**Removal target:** v1.0.0. (This gem is pre-1.0; the 0.7.x → 1.0.0 transition is the deprecation window.)
|
|
166
|
+
|
|
167
|
+
### D8. `<!-- mode: -->` HTML-comment blocks are unchanged
|
|
168
|
+
|
|
169
|
+
The block-level filter in [markdown_renderer.rb:29-45](../../../app/services/markdowndocs/markdown_renderer.rb#L29-L45) is orthogonal to whole-doc placement and continues to serve in-doc fragment filtering. No deprecation, no rename, no behavior change.
|
|
170
|
+
|
|
171
|
+
## URL Routing & Slug Validation
|
|
172
|
+
|
|
173
|
+
The current route mounts `DocsController#show` at `/docs/:slug` with the validator `SAFE_SLUG_PATTERN = /\A[a-zA-Z0-9_-]+\z/` in [docs_controller.rb:9](../../../app/controllers/markdowndocs/docs_controller.rb#L9).
|
|
174
|
+
|
|
175
|
+
Path-based routing adds a new shape: `/docs/<mode>/:slug` where `<mode>` is one of `config.modes`. Recommended routing approach:
|
|
176
|
+
|
|
177
|
+
- Add a new constrained route: `GET /docs/:mode/:slug` where `:mode` is constrained to `config.modes`.
|
|
178
|
+
- Both routes call `DocsController#show`; the controller resolves the file path from `(params[:mode], params[:slug])` using the same `SAFE_SLUG_PATTERN`.
|
|
179
|
+
- Directory traversal protection: the slug regex already prevents `..` and `/` in slugs. The mode segment is constrained by `config.modes`, so it cannot smuggle traversal.
|
|
180
|
+
|
|
181
|
+
`SAFE_SLUG_PATTERN` does not need to change — slugs remain segment-flat. Nested-subdirectory support (e.g., `docs/technical/sub/foo.md`) is **out of scope** for this design.
|
|
182
|
+
|
|
183
|
+
## Documentation Model Changes
|
|
184
|
+
|
|
185
|
+
`Markdowndocs::Documentation` (in [app/models/markdowndocs/documentation.rb](../../../app/models/markdowndocs/documentation.rb), to be edited) gains the following:
|
|
186
|
+
|
|
187
|
+
- **File discovery** walks `app/docs/*.md` (root) and `app/docs/<mode>/*.md` (for each mode in `config.modes`). Each Documentation instance is tagged with its source path; the audience is derived from the first path segment under `app/docs/`.
|
|
188
|
+
- **`Documentation#audience`** returns `Array<String>` (matches v0.6.x contract — never nil):
|
|
189
|
+
- `Markdowndocs.config.modes.dup` for shared docs at root with no `audience:` frontmatter — visible everywhere. Observationally identical to v0.6.x.
|
|
190
|
+
- `["technical"]` (single-element array) for a file at `app/docs/technical/foo.md` with no frontmatter — visible only in technical mode.
|
|
191
|
+
- The value from `audience:` frontmatter when present — frontmatter wins for backward compat, AND emits the deprecation warning. (Mixing path-scoping with `audience:` frontmatter is an unusual combination but not an error.)
|
|
192
|
+
- **`Documentation#visible_to?(mode)`** unchanged in signature, updated semantics: `nil` audience → always visible; otherwise `audience.include?(mode)`.
|
|
193
|
+
- **`Documentation.find_by_slug(slug, mode: nil)`** resolves to:
|
|
194
|
+
- `app/docs/<mode>/<slug>.md` if `mode` is non-nil AND the file exists in that subdirectory; OR
|
|
195
|
+
- `app/docs/<slug>.md` if it exists at root AND `visible_to?(mode)` returns true; OR
|
|
196
|
+
- `nil`.
|
|
197
|
+
- **`Documentation.grouped_by_category(mode: nil)`** filters the visible set: shared docs always pass; mode-scoped docs pass only when `mode` matches their audience. Empty categories are dropped (unchanged from v0.6.0).
|
|
198
|
+
|
|
199
|
+
No public API is removed. All existing call sites continue to work.
|
|
200
|
+
|
|
201
|
+
## Migration Guide (host-app-facing)
|
|
202
|
+
|
|
203
|
+
This will be inlined in the v0.7.0 release notes and CHANGELOG entry.
|
|
204
|
+
|
|
205
|
+
**URL stability.** Every existing URL continues to resolve. A host upgrading from v0.6.x to v0.7.0 without moving any files sees zero URL changes. Path-based routing introduces *new* URLs (`/docs/<mode>/<slug>`) for files that the host explicitly relocates into mode subdirectories. Search-engine indexed URLs, shared links, and bookmarks all keep working.
|
|
206
|
+
|
|
207
|
+
### If you don't use `audience:` today
|
|
208
|
+
|
|
209
|
+
No action required. Your existing root-level docs continue to render in every mode. Adopt the new convention at your leisure: create `app/docs/technical/` (or whatever mode directory you want) and move technical-only docs into it.
|
|
210
|
+
|
|
211
|
+
### If you use `audience: <single-mode>` today
|
|
212
|
+
|
|
213
|
+
For each such doc:
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
# before
|
|
217
|
+
app/docs/foo.md
|
|
218
|
+
---
|
|
219
|
+
audience: technical
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
# after
|
|
223
|
+
app/docs/technical/foo.md
|
|
224
|
+
(no `audience:` key)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The deprecation warning will surface in your logs and tell you which file to move.
|
|
228
|
+
|
|
229
|
+
### If you use `audience: [guide, technical]` today
|
|
230
|
+
|
|
231
|
+
The doc is already explicitly multi-audience. Move it to root and drop the key:
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
# before
|
|
235
|
+
app/docs/foo.md
|
|
236
|
+
---
|
|
237
|
+
audience: [guide, technical]
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
# after
|
|
241
|
+
app/docs/foo.md
|
|
242
|
+
(no `audience:` key — root = shared = visible in every mode)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### `config.categories` update
|
|
246
|
+
|
|
247
|
+
If you have technical-only docs that appear in categories on the index, prefix their slugs:
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
# before
|
|
251
|
+
config.categories = {
|
|
252
|
+
"Architecture" => %w[architecture data_model]
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
# after (technical-only architecture + shared data_model)
|
|
256
|
+
config.categories = {
|
|
257
|
+
"Architecture" => %w[technical/architecture data_model]
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Bare slugs continue to mean "the doc at the root with this name." No change needed for shared-only or guide-only categories.
|
|
262
|
+
|
|
263
|
+
## Test Surface
|
|
264
|
+
|
|
265
|
+
Each item below is a behavior that must be covered before v0.7.0 ships. Phrased as a test name suggestion.
|
|
266
|
+
|
|
267
|
+
### File discovery and visibility
|
|
268
|
+
- `Documentation.all` includes root files in every mode
|
|
269
|
+
- `Documentation.all` includes mode-subdirectory files only in that mode
|
|
270
|
+
- `Documentation.grouped_by_category(mode: "guide")` excludes `app/docs/technical/*` entries
|
|
271
|
+
- `Documentation.grouped_by_category(mode: "technical")` includes both root and `app/docs/technical/*` entries
|
|
272
|
+
- A category with no visible docs in the current mode is dropped
|
|
273
|
+
- A subdirectory whose name is NOT in `config.modes` is ignored (does not appear in any mode's listing) — and a warning is logged once per boot
|
|
274
|
+
|
|
275
|
+
### URL routing
|
|
276
|
+
- `GET /docs/billing` renders `app/docs/billing.md`
|
|
277
|
+
- `GET /docs/technical/billing` renders `app/docs/technical/billing.md` (independent doc, NOT a content-swap of the shared one)
|
|
278
|
+
- `GET /docs/technical/billing` returns the doc even when the request's mode is `guide` (D3: no access gating)
|
|
279
|
+
- `GET /docs/notamode/foo` returns 404 (the `:mode` segment must match a configured mode)
|
|
280
|
+
- `GET /docs/technical/../etc/passwd` returns 404 (slug pattern rejects)
|
|
281
|
+
|
|
282
|
+
### Mode switcher
|
|
283
|
+
- Toggling from guide → technical on `/docs/billing` (with `app/docs/technical/billing.md` present) redirects to `/docs/technical/billing`
|
|
284
|
+
- Toggling from guide → technical on `/docs/billing` (no technical sibling) stays on `/docs/billing` and updates preference
|
|
285
|
+
- Toggling from technical → guide on `/docs/technical/architecture` (no shared sibling) stays on `/docs/technical/architecture`
|
|
286
|
+
- Toggling on `/docs` (index) stays on `/docs` and the next render reflects the new mode
|
|
287
|
+
- Preference is persisted via cookie and (when configured) `user_mode_saver`
|
|
288
|
+
|
|
289
|
+
### `audience:` deprecation
|
|
290
|
+
- A doc with `audience: technical` continues to render correctly in v0.7.0
|
|
291
|
+
- A deprecation warning is logged (once per file per boot) when such a doc is loaded
|
|
292
|
+
- The warning text includes the file path and the suggested target path
|
|
293
|
+
|
|
294
|
+
### Existing block-comment filtering
|
|
295
|
+
- `<!-- mode: technical -->...<!-- /mode -->` content still strips in non-matching modes (unchanged from v0.6.x)
|
|
296
|
+
- `<!-- mode: all -->...<!-- /mode -->` content remains in every mode
|
|
297
|
+
|
|
298
|
+
## Out of Scope
|
|
299
|
+
|
|
300
|
+
These were considered and explicitly excluded from this design. Each may become its own follow-on spec.
|
|
301
|
+
|
|
302
|
+
- **Authorization / role-based access control.** Host apps gate access via standard Rails patterns ([feedback memory](../../../.claude/projects/-Users-dschmura-Documents-code-markdowndocs/memory/feedback_no_auth_in_gem.md)).
|
|
303
|
+
- **Inline browser editing.** Future feature.
|
|
304
|
+
- **"Edit on GitHub" links.** Small follow-on.
|
|
305
|
+
- **Directories as automatic categories.** `config.categories` remains the source of truth for the index. Subdirectories that match a mode are audience scopes only, not categories.
|
|
306
|
+
- **Nested mode-subdirectory paths.** `app/docs/technical/sub/foo.md` is not supported in this design. Files inside a mode subdirectory are flat.
|
|
307
|
+
- **Per-doc role/group frontmatter.** `audience:` was the closest existing analogue; it's deprecated. No new frontmatter keys for authorization.
|
|
308
|
+
|
|
309
|
+
## Open Questions
|
|
310
|
+
|
|
311
|
+
None. All decisions captured above were resolved during the 2026-05-15 brainstorming session.
|
|
@@ -5,7 +5,8 @@ module Markdowndocs
|
|
|
5
5
|
attr_accessor :docs_path, :categories, :modes, :default_mode,
|
|
6
6
|
:markdown_options, :rouge_theme, :cache_expiry,
|
|
7
7
|
:user_mode_resolver, :user_mode_saver, :search_enabled,
|
|
8
|
-
:layout
|
|
8
|
+
:layout, :allow_svg
|
|
9
|
+
attr_reader :non_mode_subdirs_warned, :audience_deprecation_emitted
|
|
9
10
|
|
|
10
11
|
def initialize
|
|
11
12
|
@docs_path = nil # Resolved lazily so Rails.root is available
|
|
@@ -19,6 +20,13 @@ module Markdowndocs
|
|
|
19
20
|
@user_mode_saver = nil
|
|
20
21
|
@search_enabled = false
|
|
21
22
|
@layout = "markdowndocs/application"
|
|
23
|
+
# Opt-in: allow a curated, safe inline-SVG subset in rendered docs.
|
|
24
|
+
# When true, the renderer passes raw HTML through commonmarker (unsafe)
|
|
25
|
+
# and the sanitizer (the security boundary) whitelists structural SVG
|
|
26
|
+
# tags/attributes while still stripping scripts/handlers. Default off.
|
|
27
|
+
@allow_svg = false
|
|
28
|
+
@non_mode_subdirs_warned = Set.new
|
|
29
|
+
@audience_deprecation_emitted = Set.new
|
|
22
30
|
end
|
|
23
31
|
|
|
24
32
|
# Lazily resolve docs_path so Rails.root is available
|
data/lib/markdowndocs/version.rb
CHANGED
data/lib/markdowndocs.rb
CHANGED
|
@@ -21,5 +21,12 @@ module Markdowndocs
|
|
|
21
21
|
def reset_configuration!
|
|
22
22
|
@configuration = Configuration.new
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
# Deprecation channel for the gem. Hosts can attach custom behaviors
|
|
26
|
+
# (e.g., raise in test, silence in production) via:
|
|
27
|
+
# Markdowndocs.deprecator.behavior = :log
|
|
28
|
+
def deprecator
|
|
29
|
+
@deprecator ||= ActiveSupport::Deprecation.new("1.0.0", "Markdowndocs")
|
|
30
|
+
end
|
|
24
31
|
end
|
|
25
32
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: markdowndocs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dave Chmura
|
|
@@ -97,6 +97,8 @@ files:
|
|
|
97
97
|
- config/importmap.rb
|
|
98
98
|
- config/locales/en.yml
|
|
99
99
|
- config/routes.rb
|
|
100
|
+
- docs/superpowers/plans/2026-05-15-path-based-audience-routing.md
|
|
101
|
+
- docs/superpowers/specs/2026-05-15-path-based-audience-routing-design.md
|
|
100
102
|
- lib/generators/markdowndocs/install/install_generator.rb
|
|
101
103
|
- lib/generators/markdowndocs/install/templates/initializer.rb
|
|
102
104
|
- lib/markdowndocs.rb
|