Files
datascape/CLAUDE.md
T
2026-08-03 19:28:03 +02:00

8.3 KiB

CLAUDE.md

I want to understand every line of code that goes into this project. Never create, edit, move, rename, or delete project files unless I explicitly ask you to do so. Instead, show me every proposed edit in the chat so I can type it in manually.

Do not run commands that modify project files, install dependencies, or change repository state unless I explicitly request that action. Instead, show me those commands in the chat so I can run them manually.

I'm an experienced developer. Do not explain syntax, APIs, programming concepts, or implementation details unless explicitly asked.

Project Overview

datascape is a minimal personal wiki where the folder structure is the wiki. No database, no CMS, no abstraction layer — every folder is a page, and index.md in a folder is that page's content.

Build & Deploy

# Local build (host architecture)
go build .

# Deploy to NAS
make deploy

Editor bundle (the one build-pipeline exception)

The page editor uses CodeMirror 6, vendored as a single pre-built IIFE at assets/editor/vendor/codemirror.bundle.js and embedded via embed.FS. This is the only deliberate exception to the "no build pipeline" rule below — it is a one-time, committed artifact, not a runtime build. go build / make deploy never touch Node and only consume the committed bundle.

Regenerate the bundle only when upgrading the @codemirror/* versions:

# bump versions in editor-build/package.json first, then:
make editor          # runs `npm ci && npm run build` in editor-build/, rewrites the vendored bundle

Commit the regenerated codemirror.bundle.js and the updated editor-build/package-lock.json. editor-build/node_modules/ is gitignored.

The bundle is served immutable under a stable filename, so the edit template appends ?v=<content-hash> to its <script> src (editorBundleVersion in main.go). The hash changes whenever the bundle bytes change, so a rebuilt bundle busts client caches automatically — no manual version bump needed.

HTTP API Surface

Method Path Behaviour
GET /{path}/ If folder exists: render index.md + list contents. If not: show empty create prompt.
GET /{path}/?edit CodeMirror 6 editor initialized with index.md content
POST /{path} Write index.md to disk; creates the folder if it does not exist yet

Non-existent paths without a trailing slash redirect to the slash form (GET only — POSTs are not redirected because path.Clean strips the trailing slash from PostURL and the content would be lost).

Code Structure

When adding a new special folder type, create a new .go file. Do not add type-specific logic to main.go or render.go.

Prefer separate, human-readable .html files over inlined HTML strings in Go. Embed them via embed.FS if needed.

Architecture Rules

  • Single binary — no installer, no runtime dependencies, no Docker
  • Go stdlib net/http only — no web framework
  • goldmark for Markdown rendering — no other Markdown libraries
  • embed.FS for all assets — no external serving, no CDN
  • No database of any kind
  • No indexing or caching unless explicitly requested and justified
  • Keep dependencies to an absolute minimum; if stdlib can do it, use stdlib

Frontend Rules

  • Vanilla JS only — no frameworks, no build pipeline (the single exception is the vendored CodeMirror editor bundle; see Build & Deploy)
  • Each feature gets its own JS file; global behaviour goes in global-shortcuts.js
  • Do not inline JS in templates or merge unrelated features into one file
  • ALT+SHIFT is the modifier for all keyboard shortcuts — do not introduce others
  • Editor toolbar buttons use data-action + data-key; adding data-key auto-registers the shortcut
  • The editor is a mode of a page. Opening it pushes a normal history entry so the browser/Android Back button cancels the edit and returns to the page — this "back to cancel" gesture takes priority. history-nav.js only rewrites the way out: leaving the editor via the same-page CANCEL link uses location.replace so the editor entry is collapsed rather than sandwiched between two page entries (which would let Back walk back into the editor). SAVE does the equivalent — it POSTs via fetch and then replaceStates the editor entry with the saved page, so the editor never lingers in history once you leave it (the pre-save page snapshot one step back is the accepted cost). Links to a different page's editor (new page / new child) push normally. The save POST answers 204 + X-Target when the request carries X-Save-Mode: replace, because the target may hold a #section anchor only the server can compute and fetch drops fragments from followed redirects.
  • For mutating modals (anything that POSTs and then navigates), call closeModal() and then postReplace(action, body, target) from page/actions.js. Do NOT use <form>.submit(). Two reasons:
    1. The modal must be removed from the DOM before navigation, or the browser's bfcache snapshots it open and back-nav restores the modal.
    2. postReplace uses window.location.replace so the action + result occupy a single history entry. A naive POST → 303 → GET creates two entries, and back-nav lands on a stale pre-mutation snapshot of the same page.

CSS

Follow SMACSS conventions (Scalable and Modular Architecture for CSS). The stylesheet is organized into five categories:

  • Base — element resets and global defaults only. Never style header, textarea, input, aside, footer, etc. directly for visual treatment — always via a class.
  • Layout.row, .col, .page-wrap. Use these for flex layout; do not inline display: flex on feature classes.
  • Modules — reusable components: .panel, .panel-header, .menu-row, .btn, .input, .muted, .truncate, etc. New visual patterns should reuse these. Before adding a new module, check whether an existing one + a modifier already covers the case.
  • State.is-* prefix only (.is-open, .is-selected, .is-active, .is-disabled, .is-empty). State is the only place a class describes a moment in time rather than a structural role.
  • Theme — colors, borders, spacing, and font sizes come from CSS variables defined in :root (--bg, --secondary, --border, --border-dashed, --space-*, --font-*). No hardcoded 1px solid #..., no hardcoded rem spacing in component rules.

Naming: flat-dash (.panel-header, .btn-small), not BEM (.panel__header--small). Modifiers attach as additional classes (<div class="btn btn-small">), not as new standalone classes.

Anti-patterns to reject:

  • One-off classes that duplicate an existing module (.save-button when .btn exists, .form-name-input when .input exists).
  • Element selectors (textarea { ... }, header { ... }) for visual treatment — add a class instead.
  • Inlining display: flex; gap: X on a feature class instead of composing with .row / .col.
  • Adding a new module for a single use site — prefer a modifier on an existing module first.
  • Hardcoded colors, border widths, or spacing values inside component rules — pull a variable, or add one to :root if it's missing.

Development Priorities

When building features, apply this order:

  1. Correctness on the filesystem — never corrupt or lose files
  2. Mobile usability (primary editing device is Android over Wireguard VPN)
  3. Simplicity of implementation, adhere to KISS
  4. Performance

Date Formatting

  • General UI dates (file listings, metadata): ISO YYYY-MM-DD
  • Diary headings (year/month/day) are also ISO short form: # 2026, ## 2026-05, ### 2026-05-28. No long-form rendering.
  • Calendar widget month names are German; the germanMonths map in diary.go keeps the labels keyed by time.Month since Go's time.Format is English-only.

What to Avoid

  • Any parallel folder structure (e.g. a separate media/ tree mirroring pages/)
  • Over-engineering auth — Basic auth is sufficient for a personal VPN tool
  • Heavy payloads or expensive rendering (target CPU: ARMv7 32-bit NAS)
  • Suggesting Docker (plain binary is preferred)

Out of Scope (do not implement unless explicitly asked)

  • Full-text search
  • Browser-based file upload
  • Version history / git integration
  • Multi-user support
  • Tagging or metadata beyond index.md content