Files
datascape/AGENTS.md
2026-04-13 13:11:34 +02:00

6.4 KiB

AGENTS.md — Personal Wiki (go-wiki)

Project Philosophy

This is a minimal personal wiki where the folder structure is the wiki. There is no database, no CMS, no abstraction layer between the user and their files. Every decision should reinforce this — if a proposed solution adds indirection between the filesystem and what the user sees, question it.

Core Concept

  • Every folder is a page
  • index.md in a folder is that page's content
  • All related files (PDFs, images, CAD files, etc.) live in the same folder as index.md
  • Image links in index.md like ![](photo.jpg) work because siblings are served at the same path
  • There are no "attachments" — files are just files in a folder

Target Environment

  • Runs on a QNAP TS-431P3 NAS (Annapurna Labs AL-314, ARMv7 32-bit, linux/arm)
  • All files live on the NAS and are mounted/accessed locally by the binary
  • Users access via browser over Wireguard VPN from Windows, Linux, and Android
  • Must cross-compile cleanly: GOARCH=arm GOOS=linux GOARM=7 go build

Tech Constraints

  • Language: Go
  • Output: Single static binary, no installer, no runtime dependencies
  • Markdown: goldmark for server-side rendering — no other markdown libraries
  • Assets: Embedded via embed.FS — no external asset serving or CDN
  • HTTP: stdlib net/http only — no web framework
  • Dependencies: Keep to an absolute minimum. If stdlib can do it, use stdlib.

HTTP Interface

The entire API surface should stay minimal:

Method Path Behaviour
GET /{path}/ If folder exists: render index.md + list contents. If not: show empty create prompt.
GET /{path}/?edit Mobile-friendly editor with index.md content in a textarea
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).

Do not add endpoints beyond these without a concrete stated need.

UI Principles

  • Mobile-first — the primary editing device is Android over Wireguard
  • No JavaScript frameworks — vanilla JS only, and only when necessary
  • No build pipeline for frontend assets — what is embedded is what is served
  • Readable on small screens without zooming
  • Fast on a low-power ARM CPU — no heavy rendering, no large payloads

Frontend Conventions

JS file scoping: each feature gets its own file. Global app behaviour goes in global-shortcuts.js. Feature-specific logic gets its own file (e.g. editor.js). Do not inline JS in the template or consolidate unrelated features into one file.

Keyboard shortcuts: ALT+SHIFT is the established modifier for all application shortcuts — it avoids collisions with browser and OS bindings. Do not use other modifiers for new shortcuts.

Editor toolbar: buttons use data-action (maps to a JS action function) and data-key (the ALT+SHIFT+KEY shortcut letter). Adding a data-key to a button automatically registers its shortcut — no extra wiring needed.

Code Structure

The backend is split across three files:

File Responsibility
main.go Server setup, routing, serveDir, handlePost, pageTypeHandler interface, readPageSettings
render.go Shared helpers: markdown rendering, heading extraction, file listing, icons, formatting
diary.go Diary page type: all types, templates, and render functions

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

Special Folder Types (pageTypeHandler)

Folders can opt into special rendering by placing a .page-settings file in them. Format: one key = value per line; # lines are comments.

# example
type = diary

The server walks up from the requested path looking for a .page-settings file. When found, it determines the depth of the current path relative to that root and dispatches to the matching pageTypeHandler.

Interface (defined in main.go):

type specialPage struct {
    Content         template.HTML
    SuppressListing bool
}

type pageTypeHandler interface {
    handle(root, fsPath, urlPath string) *specialPage
}

handle returns nil when the handler does not apply. SuppressListing hides the default file/folder table (used when the special content replaces it).

Registering a new type: implement the interface in a new file and register via init():

func init() {
    pageTypeHandlers = append(pageTypeHandlers, &myHandler{})
}

serveDir iterates pageTypeHandlers and uses the first non-nil result. It has no knowledge of specific types.

Diary type (diary.go)

Activated by type = diary in a .page-settings file. Folder structure:

Root/                        ← .page-settings (type = diary)
  YYYY/                      ← depth 1 — year view (month sections + photo counts)
    YYYY-MM-DD Description.ext  ← photos live here, named with date prefix
    MM/                      ← depth 2 — month view (day sections with content + photos)
      DD/                    ← depth 3 — day view (index.md content + photo grid)
        index.md

Photos are associated to days by parsing the YYYY-MM-DD prefix from filenames in the year folder. No thumbnailing is performed — images are served at full resolution with loading="lazy". The year view shows only photo counts, not grids, for performance.

Auth

  • Basic auth is sufficient — this is a personal tool on a private VPN
  • Do not over-engineer access control

What to Avoid

  • No database of any kind
  • No indexing or caching layer unless explicitly requested and justified
  • No parallel folder structures (the DokuWiki anti-pattern: pages/ mirrored by media/)
  • No frameworks (web, ORM, DI, etc.)
  • No build steps for frontend assets
  • Do not suggest Docker unless the user asks — a plain binary is preferred

Development Order

When building new features, follow this priority order:

  1. Correctness on the filesystem (never corrupt or lose user files)
  2. Mobile usability
  3. Simplicity of implementation
  4. Performance

Out of Scope (for now)

These are explicitly deferred — do not implement or scaffold unless asked:

  • Full-text search
  • File upload via browser
  • Version history / git integration
  • Multi-user support
  • Tagging or metadata beyond index.md content