Pre-WYSIWYG UI work + security issue sweep — Design

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 is docs/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:

0. Why

Two programs come before a rich-text (WYSIWYG) editor is designed.

  1. Freeze the syntax surface. A rich-text editor needs a schema node or mark, plus a serializer, for every construct. Anything added after it ships gets built twice. So the outstanding Obsidian syntax lands first: Phase 2 (highlights and comments) and Phase 4 (inline tags). So does the paste-pipeline gap for embedded images, which the WYSIWYG paste path will reuse. Obsidian Phase 3 (reader task toggles) is independent and out of scope.
  2. Close the security-adjacent issues filed during the 2.4.58 display-names work: #104, #105, #106 and #107.

Anti-goals (a failure even if everything "works")

1. Obsidian Phase 2 — ==highlight== and %% comments %%

The normative spec is ObsidianBlockReferencesDesign, §"Phase 2".

2. Obsidian Phase 4 — inline #tags

3. Paste as Markdown: upload embedded images

4. #106 — the configuration reference page restricted its own editing

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.

5. #104 — one reader of access rules

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:

Final classification:

ShapeResult
canonical one-line rule in proserule
wrapped over lines (no blank line)rule (lines joined with single spaces)
keyword or permission in any caserule
whitespace after the bracerule
escaped ruleliteral text
inside a code span or fenceliteral text
DENY, in any caselocked
other words starting with ALLOWlocked
inside an HTML block, math, frontmatter or a linklocked
a permission the parser rejectslocked
a wrapped break inside a namelocked
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.

6. #105 — bounded render cost

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.

7. #107 — deleted accounts keep their handle on old content

8. Delivery

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.