All notable changes to git-cms are documented in this file.
Unreleased — git-stunts branch
- Content Identity Policy (M1.1): Canonical slug validation with NFKC normalization, reserved word rejection, and
CmsValidationErrorcontract (ContentIdentityPolicy.js) - State Machine (M1.2): Explicit draft/published/unpublished/reverted states with enforced transition rules (
ContentStatePolicy.js) - Admin UI overhaul: Split/edit/preview markdown editor (via
marked), autosave, toast notifications, skeleton loading, drag-and-drop file uploads, metadata trailer editor, keyboard shortcuts (Cmd+S,Esc), dark mode token system - DI seam in CmsService: Optional
graphconstructor param enablesInMemoryGraphAdapterinjection for zero-subprocess tests - In-memory test adapter: Unit tests run in ~11ms instead of hundreds of ms (no
git init/subprocess forks) - E2E test separation: Real-git smoke tests in
test/git-e2e.test.js, excluded from defaulttest:localruns test:git-e2escript: Run real-git integration tests independently@git-stunts/alfreddependency: Resilience policy library (wired but not yet integrated)@git-stunts/docker-guarddependency: Docker isolation helpers- ROADMAP.md: M0–M6 milestone plan with blocking graph
- Formal LaTeX ADR (
docs/adr-tex-2/) - Onboarding scripts:
setup.sh,demo.sh,quickstart.shwith interactive menus - Dependency integrity check:
check-dependency-integrity.mjspreventsfile:path regressions
- CmsService now uses
@git-stunts/git-warpGitGraphAdapterand@git-stunts/plumbingGitRepositoryServiceinstead of raw plumbing calls - All
repo.updateRef()calls routed throughCmsService._updateRef()for DI/production dual-path listArticles()supports both plumbing (for-each-ref) and in-memory (graph.listRefs) paths- Server endpoints return structured
{ code, field }errors for validation failures - Swapped all
file:dependency paths to versioned npm ranges (PP3)
- Symlink traversal hardening in static file serving
- Slug canonicalization enforced at all API ingress points
- Admin UI API calls aligned with server contract (query params, response shapes)
- Server integration test environment stabilized for CI
- (P1) Stored XSS via markdown preview: Sanitize
marked.parse()output with DOMPurify - (P1) Unpublish atomicity: Reorder
unpublishArticleso draft ref updates before published ref deletion - (P2) XSS via slug/badge rendering: Use
textContentand DOM APIs instead ofinnerHTMLinterpolation - (P2) SRI hashes: Add
integrity+crossoriginto marked and DOMPurify CDN script tags - (P2) Null guards:
revertArticleandunpublishArticlethrowno_draftwhen draft ref is missing;_resolveArticleStatethrowsarticle_not_foundwhen both draft and published refs are missing - (P2) uploadAsset DI guard: Throw
unsupported_in_di_modewhencas/vaultare null - (P2) Trailer key casing: Use camelCase
updatedAtinunpublishArticleandrevertArticle(was lowercaseupdatedatwhich brokerenderBadgeslookups); destructure out decoded lowercase key before spreading to avoidTrailerInvalidError - (P2) XSS in
escAttr: Escape single quotes ('→') to prevent injection into single-quoted attributes - (P2) Supply-chain hardening: Vendor Open Props CSS files locally (
public/css/) instead of@importfrom unpkg, eliminating CDN dependency and SRI gap - (P2) Monkey-patch safety: E2E test restores
plumbing.executeinfinallyblock - Unknown
draftStatusinresolveEffectiveStatenow throwsunknown_statusinstead of silently falling through to draft - Removed double-canonicalization in
_resolveArticleState - Replaced sequential
readRefloop withPromise.allinlistArticlesDI path - Admin UI: fixed
removeTrailerRowredundant positional removal, FileReader error handling, autosave-while-saving guard, Escape key scoped to editor panel, drag-and-drop scoped to drop zone - Test cleanup: extracted
createTestCms()helper, converted try/catch assertions to.rejects.toMatchObject(), added guard-path tests TRANSITIONSSets nowObject.freezed to prevent mutation via.add()/.delete()- DI-mode
_updateRefnow performs manual CAS check againstoldSha - Server tests assert setup call status codes to surface silent failures
- Vitest exclude glob
test/git-e2e*→test/git-e2e**to cover future subdirectories