7.5 KiB
phase, plan, status, subsystem, tags, requires, provides, affects, tech-stack, key-files, decisions, metrics, actuals, commits, plan_head_before, plan_head_after
| phase | plan | status | subsystem | tags | requires | provides | affects | tech-stack | key-files | decisions | metrics | actuals | commits | plan_head_before | plan_head_after | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-261006-sne | 01 | complete | cabana admin API + admin SPA |
|
|
|
|
|
|
|
|
|
3 | 0ff928d6cf |
27711b7c21 |
Quick 261006-sne: Render markdown field preview via sanitized server HTML Summary
The admin markdown Preview now renders headings, lists and links. The HTML comes from a new backend-guarded, CSRF-protected POST {prefix}/api/v1/markdown/preview route that runs cabana.RenderMarkdown. MarkdownField binds only that answer, and on mlmarkdown fields the preview follows the active locale.
Tasks
| Task | Name | Commit | Key files |
|---|---|---|---|
| 1 | Server route POST /markdown/preview (tracer) | b492e79 |
field_markdown.go, http.go, admin_openapi.go, Go tests, admin.json, schema.d.ts, README, forms.md |
| 2 | MarkdownField Preview renders server-sanitized HTML, rebuilt dist | a0116df |
MarkdownField.vue, main.css, MarkdownField.test.ts, MLFields.test.ts, forms.md, modules/boardwalk/dist |
| 3 | Raw-HTML gates allow the single sanctioned binding | 27711b7 |
check-phase10/12.1/12.2/14.2.1.sh |
What was built
- Route:
service.markdownPreviewre-checks the backend principal and answers 401unauthenticatedwithout one. It decodes{markdown}throughdecodeStrictBody: unknown keys, malformed JSON and trailing data get 422, and a body over the cap gets 413. It renders throughRenderMarkdownand answers{html}. When the gate refuses the output, the answer is 422validation_failedwith a fixed message onmarkdown. The route is mounted in thebackendguard group behindrequireAjax.RenderMarkdownand its gate are unchanged. - Tracer gate: the route ran end to end through guard, CSRF, renderer and OpenAPI conformance (
TestMarkdownPreviewRoute,TestPhase10OpenAPIConformance) before the SPA work started. - SPA: Preview fetches when it opens. While it is open, a source change triggers one re-fetch after 300 ms. A sequence counter drops stale answers. Blank sources send nothing. Error answers show the server's
details.markdown[0]as text, and failed requests show "Preview unavailable.".aria-busyreflects loading. - Styles:
.summer-markdownstyle kit with low-specificity:where()selectors that read only--c-*variables and--font-mono.
Verification
go vet ./...andgo test ./...passed before each of the three commits (testcontainers Postgres included).scripts/check-admin-openapi.sh --check,scripts/check-admin-dist.shandgo test ./cmd/summer -run TestDocsTreeall pass.npm --prefix admin testpasses (73 files, 1054 tests), andnpm --prefix admin run typecheckis clean.check-phase12.1.sh --hygieneandcheck-phase12.2.sh --hygienenow pass; both failed before this change.check-phase14.2.1.sh --forbiddenpasses.check-phase10.sh --hygieneno longer reports a raw-HTML directive.- The exemption was tested against the planted
<div v-html="raw" />and against the variants<div v-html="sanitizedHtml" />andv-html="other"in MarkdownField.vue, using the real GNU grep. All three are still refused.
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking] Second route inventory in phase10_coverage_test.go
- Found during: Task 1 (full
go test ./...) - Issue:
TestPhase10Coverage/every_unsafe_mounted_route_is_CSRF-walkedkeeps its own sorted list of unsafe routes and says "expects 25". The plan did not list this file. - Fix: Added
POST /markdown/previewto the list in sort order and changed the message to 26. - Files modified: modules/cabana/phase10_coverage_test.go
- Commit:
b492e79
2. [Rule 1 - Bug] vue-tsc noUncheckedIndexedAccess in the new vitest file
- Found during: Task 2 typecheck
- Fix: Added non-null assertions on indexed
sent[n]/pending[n]accesses in MarkdownField.test.ts. - Commit:
a0116df
Small additions beyond the plan's behavior list:
- A trailing-data case in the Go invalid-body test.
- Closing Preview or unmounting bumps the sequence, so an answer still in flight is dropped.
Pre-existing issues (not touched)
These scripts/check-phase10.sh --hygiene refusals predate this task and are out of scope:
admin/src/api/files.ts:150usesnew XMLHttpRequest(), which the direct-fetch rule catches.admin/src/api/files.tsis an unexpected file inadmin/src/api.admin/src/components/form/fields/FileCaptionModal.vueis imported by no test.admin/src/components/form/mlLocale.tsis imported by no test.
Because of them, scripts/check-phase10.sh --self-test stops with "rejected the clean scratch copy" before it plants anything. The plant refusal was checked by hand instead (see Verification).
Threat Flags
None beyond the plan's threat model. The one new surface, the T-261006sne-01..07 route and the raw-HTML sink, is mitigated as the plan specifies.
Known Stubs
None.
Manual UAT (not gating)
- Rebuild and restart the proof host.
- Open a markdown field at /backend and click Preview.
# Titleshould render as a heading. - Open an mlmarkdown field, click Preview and switch the locale. The preview should follow the locale.