- Go 1.26.4+
golangci-lintformake lint(install withmake deps)
git clone https://github.com/polymorcodeus/park.git
cd park
make buildmake check # fmt, vet, lint, test- Keep PRs focused: one behavior change per PR.
- Update
README.mdandCONTRIBUTING.mdwhen behavior, commands, or flags change; docs are part of done. - Follow the conventions in this file (package boundaries, error wrapping, message ownership).
| Package | Does | Imports |
|---|---|---|
schema |
public frontmatter contract; canonical categories, version, and write template | stdlib only |
internal/config |
configuration schema, loading, validation | internal/fs, schema |
internal/note |
note content, frontmatter parsing/writing, creation, ingestion (Note, Parse, Write, Add, Create) |
internal/config, internal/fs, schema |
internal/store |
on-disk item management, scanning, reclassification, list formatting | internal/config, internal/note, schema |
internal/render |
glamour-based rendering | internal/note |
internal/theme |
color constants | stdlib only |
internal/fs |
filesystem helpers (ExpandPath) | stdlib only |
internal/model |
Bubble Tea TUI screens | internal/config, internal/note, internal/store, internal/theme |
cmd/park |
CLI tree + wiring | everything |
These rules keep the package boundaries above meaningful as the codebase grows.
internal/fsis the only package that expands~and$HOME. Ingestion paths (Draft.FromFile) are normalized exactly once, innote.IngestFile, so parsing, form preview, and source-file removal all see the same path.internal/noteowns all frontmatter parsing and writing. Code outside this package should not parse---blocks by hand.internal/storeowns category-folder operations, resolving filenames to full paths, andpark listgrouping/formatting (plain and JSON). Filename resolution is unified inResolvePath: a bare basename is searched across every configured category folder, while a value containing a path separator is treated as a literal path and never joined onto a category folder.park showandpark reclassifyboth resolve their<file>argument this way, so a path can never double-join.internal/modelmay callnote.Create,store.Scan, andstore.Reclassify, but should not read files directly from disk except through those packages.cmd/parkparses CLI flags and delegates all file/content work tointernal/noteorinternal/store.
schema.Frontmatteris the canonical metadata block:Category,Created,Source,Synopsis. It lives in the publicschemapackage so downstream tooling can import the contract instead of re-deriving it.note.Metadatais an alias toschema.Frontmatter. It is not validated in isolation because its completeness depends on context.note.Draftis the creation-time model:Filename,Body,FromFile, plusMetadata.Createdmay be empty; it is populated when the draft is converted to a note.Draft.ReadyToCreate()checksFilename,Metadata.Category,Metadata.Source, andMetadata.Synopsis.note.Noteis the persisted model:Path,Body, plus completeMetadata(Createdalways set). Completeness checks go throughschema.Frontmatter.IsComplete(); there is noNote-level completeness wrapper.store.Itemis the read/scanned model. It embedsnote.MetadataplusPath,Filename, andModTime.store.Groupis a category name plus its[]Item;store.Listreturns groups in config order andstore.FormatList/store.WriteListJSONrender them forpark list.Bodyhas the same meaning inDraftandNote(markdown content below the frontmatter). DuringDraft→Noteconversion, any embedded frontmatter inBodyis stripped and merged intoMetadata, soNote.Bodyis always clean.
- Key bindings are matched in
Updatefrom the samekey.Bindingvalues that render help (seecategoryBindingininternal/model/assist.go, which pairs each category name with its binding), so help text and behavior cannot drift.
- Errors are wrapped with
fmt.Errorf("...: %w", err)when crossing package boundaries. - User-facing message formatting belongs in the package that owns the data.
cmd/parkwires output to the terminal but does not format store results. - Category-validation errors are constructed once, by
Config.UnknownCategoryError; CLIBeforehooks and domain packages call it instead of hand-writing the "unknown category" message. - Avoid
init()for logic that can be explicit inmain()or a constructor. - Exported helpers with no callers get deleted, not kept "just in case": the
unusedlinter cannot see exported identifiers, so dead API only surfaces in review.
The TUI uses the Charm design system tokens:
- Page background:
#14121a - Raised surface:
#1c1a24 - Primary text:
#f5f1fa - Muted text:
#a79fc0 - Faint text:
#6f6785 - Accent purple:
#7d56f4 - Accent pink:
#FF4081
Colors are defined as exported constants in internal/theme/theme.go so both
the TUI (internal/model) and the CLI's styled error output (cmd/park)
share the same palette.
- Frontmatter is flat
key: valueparsed line-by-line (no YAML dependency). Reclassifyrewrites frontmatter before moving the file, so a failed move never leaves a file in an inconsistent state.schemais a public, stdlib-only package. Other tools can importgithub.com/polymorcodeus/park/schemato pin the frontmatter contract.internal/confighas no dependency onmain.go; importing the focused packages into another CLI is just wiring commands to the exported functions.Configis loaded once in the CLIBeforehook and passed as*config.Configto all command helpers.