Status: shipped in 2.5.6 (deployed 2026-11-01). Issues #104, #105, #106 and #107 are closed. The repo copy is
docs/superpowers/specs/2026-11-01-pre-wysiwyg-ui-and-security-design.md, and the production access-rule audit isdocs/superpowers/specs/2026-11-01-acl-shape-audit.md(0 locked and 0 changed across 1466 prod pages, before and after the deploy).Decisions added during the build, beyond this document:
- A
DENYwritten in any case, or after any whitespace, locks the page. So does a wrapped rule whose line break falls inside a name with no comma beside it (WRAPPED_NAME).- An access rule inside a
%% comment %%is still read as a rule and locks the page (IN_COMMENT). The same goes for a rule that becomes code or stops being code once comments are stripped.- The save-time check runs at the save seam, on the final text after every filter has run. Refusals list at most 50 violations plus a total.
- Comments are hidden from every reader surface for callers without edit. The server-rendered page and
?format=mdare reader text for every caller; editors get the raw text from/api/pages.- Tags keep their written form for display and in the ontology. They are normalised only where they are compared or counted, and tag counts cover only the pages the caller can view.
- The #105 parse budget is disarmed once a parse succeeds.
Two programs come before a rich-text (WYSIWYG) editor is designed.
==highlight== and %% comments %%The normative spec is ObsidianBlockReferencesDesign, §"Phase 2".
cm-lp-mark) that hides the == delimiters except on the caret's line. Comments are dimmed (cm-lp-comment) and keep their delimiters visible.Mod-Shift-H toggles a highlight. Mod-/ toggles a comment and takes precedence over CodeMirror's default toggleComment. Both commands are also in the command palette.<mark> becomes ==…==.== or %% in prod or in the repo.#tagsInlineTagSyntax (wikantik-api) is the one scanner. A tag needs at least one non-digit, so issue numbers like #104 are not tags. Code, math, comments, headings, raw HTML, links and [{…}]() spans are excluded.<a class="tag inline-tag" href="/search?tag=…">. The tag view is /search?tag=x, with pagination.# in prose offers completions from the tag distribution.list_pages_by_filter, list_pages and export-by-tag use frontmatter tags plus inline tags, normalised. SEO, feeds and JSON-LD use frontmatter tags only.data: URIs of raster images (PNG, JPEG, GIF, WebP) are decoded to files and uploaded as page attachments, each placed where it appeared in the pasted content.blob:, file: and cid: images are dropped and counted.The wikantik.aclManager description contained an access-rule example in plain prose, so the rule was enforced on the generated page. The generator now wraps any [{…}]() in prose in backticks. The regenerated page was pushed to prod.
Before: two readers fed DefaultAclManager.parseAcl. One was a text scan over the full text, and the other was the render path, which mutated the page's cached ACL. Enforcement therefore depended on order, and the two readers disagreed on wrapped rules, DENY, case, HTML blocks, math, frontmatter, links, escapes and leading spaces.
After:
AccessRules.read(fullText) is the only source of an enforced ACL. It returns Rules or Locked.AllPermission holders can use the page. Admins see a banner with the reasons and line numbers, and /admin/content/locked-pages lists every locked page.ACCESS_RULE_SHAPE, or the MCP refusal). An unchanged disputed span already present in the previous version is allowed through.Final classification:
| Shape | Result |
|---|---|
| canonical one-line rule in prose | rule |
| wrapped over lines (no blank line) | rule (lines joined with single spaces) |
| keyword or permission in any case | rule |
| whitespace after the brace | rule |
| escaped rule | literal text |
| inside a code span or fence | literal text |
DENY, in any case | locked |
| other words starting with ALLOW | locked |
| inside an HTML block, math, frontmatter or a link | locked |
| a permission the parser rejects | locked |
| a wrapped break inside a name | locked |
inside a %% comment %% | locked |
Audit: AclShapeAuditCli (wikantik-extract-cli) compares the new reading with both old readings for every page. Run it with --dir on a byte-exact copy of the page store.
The flexmark attributes extension could go quadratic, or overflow the stack, on crafted {…} runs. Every parse now runs under a deterministic read budget (BudgetedText). When the budget runs out, the page is re-parsed without the attributes extension, and failing that it is shown as escaped preformatted text. Each fallback is logged. The budget is disarmed after a successful parse, so a cached document never exhausts it on re-render.
deleted_logins (V065), and the delete fails if that write fails.Items shipped in this order: #106, #105, #104 (lock down, open up, audit), #107, Phase 2, Phase 4, paste images. Each item had a failing test first and a task review. A final whole-branch review followed, and the release gate was bin/run-tests.sh --all plus vitest --coverage.