Books

https://code.bas.es/arne/books last activity · 6m

Ideas

Design

History

2026-04-24
2026-04-20
2026-04-14
show earlier entries
2026-04-13
    • Progress bar background uses color-mix(--margin 50%, --paper-soft) so the leaf-coloured fill stands out more against the page rule lines.
    • store.SearchBooks matches series in addition to title and author.

    Closes #15.

    See on code.bas.es →

  • Summary

    • Spec: docs/superpowers/specs/2026-04-13-htmx-shelve-search-design.md
    • Vendors htmx 4 as a static asset; loads it from base.html.
    • Library search: debounced, fragment-swap on input via htmx.
    • Shelve toggle on book detail page: htmx-driven partial swap.
    • Shelf page rows: htmx delete swap removes the row inline.
    • Header: visible sign-in / sign-out link for visitors and users.

    Test plan

    • [ ] go test ./... green
    • [ ] Manual: type into the library search box; grid updates without reload, URL updates.
    • [ ] Manual: shelve / unshelve from book detail page; toggle button updates inline.
    • [ ] Manual: remove a book from the shelf page; row vanishes inline.
    • [ ] Manual: log out; verify "sign in" link appears in header.

    See on code.bas.es →

  • Summary

    • Spec: docs/superpowers/specs/2026-04-13-admin-api-design.md
    • Implements the typed /api/admin/* surface that replaces direct-SQLite metadata edits.
    • New package internal/adminapi; new store methods in internal/store/admin.go.

    Test plan

    • [ ] go test ./... green
    • [ ] Manual smoke: curl -H "Authorization: Bearer $BOOKS_ADMIN_TOKEN" $PUBLIC/api/admin/books?q=tolkien
    • [ ] Verify PATCH diff response shape on a real book
    • [ ] Verify 404 on /api/admin/* when BOOKS_ADMIN_TOKEN is unset

    See on code.bas.es →

  • Summary

    First of three roadmap changes (auth boundary, admin API, htmx). Splits the site into five auth tiers:

    • visitor — library, book detail, author, series are now public
    • user — any OIDC-authenticated identity can download /file/{uuid}
    • owner — shelve/edit/delete/upload/errata require the owner sub
    • kobo — existing token-in-URL flow untouched
    • admin — new /api/admin/* surface behind Authorization: Bearer $BOOKS_ADMIN_TOKEN (placeholder endpoints; real operations land in spec 2)

    Specs and roadmap notes live in docs/superpowers/.

    Test plan

    • [ ] Visit https://books.fismen.no in a private window → library loads without login
    • [ ] Log in and confirm shelf, edit, upload, delete still work
    • [ ] curl /api/admin/books → 401
    • [ ] curl -H "Authorization: Bearer \$BOOKS_ADMIN" /api/admin/books → 404 (placeholder)

    See on code.bas.es →

  • Summary

    • Author names and series names are now clickable links throughout the UI
    • /author?name=... shows all books by that author
    • /series?name=... shows all books in a series, ordered by series index

    Test plan

    • [ ] Click an author name on the library page — should show their books
    • [ ] Click a series tag on the library page — should show the series
    • [ ] Click author/series on the book detail page
    • [ ] Verify multi-author bylines link each author separately

    🤖 Generated with Claude Code

    See on code.bas.es →

  • Summary

    • Reading progress shown on shelf entries and book page as a hair-thin leaf-green fore-edge rule
    • New --leaf semantic color token reserved for reading state
    • New book metadata fields: read_state (unread/reading/read, manual with Kobo auto-promotion at ≥95%), first_published_year, goodreads_url
    • Shelf now sorts reading-first (by progress desc), then unread, then read

    Design spec: docs/superpowers/specs/2026-04-13-shelf-progress-and-metadata-design.md Implementation plan: docs/superpowers/plans/2026-04-13-shelf-progress-and-metadata.md

    Test plan

    • [ ] Verify shelf shows progress rule with correct fill for books with Kobo progress
    • [ ] Verify shelf sort order: reading (by progress desc) → unread → read
    • [ ] Edit form: set read state, first published year, goodreads URL on a book; verify persistence and display
    • [ ] Book page: verify goodreads arrow link opens externally

    🤖 Generated with Claude Code

    See on code.bas.es →

2026-04-12
  • Summary

    • Store and emit ContentSourceProgressPercent for Kobo reading progress
    • Kepubify epub files at download time for proper Kobo rendering

    Test plan

    • [ ] Verify Kobo sync picks up reading progress
    • [ ] Verify downloaded epubs are kepubified

    🤖 Generated with Claude Code

    See on code.bas.es →

2026-04-11
  • Summary

    Implements the full curation spec from docs/superpowers/specs/2026-04-11-curation-and-nav-design.md: delete books from the web UI, upload books through a header action, and replace the corner dropdown with a thin top-bar nav. Plus an incidental but necessary Kobo bug fix that the delete flow would otherwise trip.

    Twenty small commits, one per plan task (with two gofmt fixups along the way), closed out through Tasks 1–18 of docs/superpowers/plans/2026-04-11-curation-and-nav.md. Full go test ./... green across all nine packages. Final full-branch code review completed against the spec — one Important finding (EXDEV safety in library.Archive/Restore) was addressed inline in the last commit.

    What you get

    • Delete books from the UI. Book detail page has a two-click inline-confirm delete book button. First click arms it in stamp red with a "really?" prefix; second click within 5s commits. No modal. Soft-delete: the row stays (so Kobo removal tracking can still find the entitlement UUID), the files move to <BOOKS_DATA_DIR>/archived-books/<hash>.<ext>, the book auto-unshelves in the same DB transaction.
    • Upload books from the header. + upload action in the site header opens a hidden file picker, POSTs the .epub to /upload, runs it through the same importer pipeline as the inbox watcher, and lands on the new book's detail page. Uploading a file whose hash matches a previously-archived book restores the original row from the archive instead of creating a duplicate — the book's ID, UUID, and all metadata survive the round-trip. 50 MB cap enforced before ParseMultipartForm is called.
    • Thin site header. Corner dropdown is gone. Every page now carries a top-aligned shelf · library · errata nav with an active-state stamp underline, plus the upload action on the right. Active page is driven by the existing PageData.Section field. The ← library back link on the book detail page went away with it — the library tab in the header now serves that role.
    • Kobo removal bug fix. buildRemovedEntitlement used to synthesize a fake UUID via uuidFromInt(bookID). In the pre-delete world this never mattered — nothing ever routed a book through the removed branch after we'd previously delivered a real UUID. The moment delete-of-a-shelved-book ships, every removal envelope would carry a UUID Nickel never saw, and the device would silently fail to forget the book. Fixed by a new store.GetBookByIDIncludingDeleted method that survives the WHERE deleted_at IS NULL filter, plus changing buildRemovedEntitlement(book) to read book.UUID via entitlementID. uuidFromInt deleted as dead code. Regression test pinned.
    • Flash messages. Short one-string messages after redirects ("deleted X", "already imported as Y"). One documented deviation from the spec: the spec called for an HMAC-signed cookie, but the auth package has no signing key and adding one for a single string was over-engineering. Instead, flashes travel through a ?flash=<url-encoded> query parameter on the redirect target. html/template auto-escapes on render (&#34; in the test assertions proves it), the flash clears on the next navigation when the URL no longer carries the parameter, and an explicit test asserts the XSS-safe rendering path. Single-user tool, minimal surface area, no new crypto.

    What's not in this PR (deliberate)

    • No bulk delete.
    • No visible archive view — restore is by re-upload (detected via file hash).
    • No drag-and-drop upload. Explicitly cut during brainstorming; the header button is the whole upload UX.
    • No hard delete / GC of archived files.
    • No orphan cleanup on book_authors / device_removals / reading_state / kobo_sync_session_books — those rows stay put and the Kobo delta still handles them correctly.

    File structure

    Plan-compliant: new handlers (delete.go, upload.go) are their own files in internal/web/, test files (delete_test.go, upload_test.go, flash_test.go, roundtrip_test.go) similarly split. No monster-file growth.

    Test plan

    • [x] go test ./... — all 9 packages pass
    • [x] go build ./... — clean
    • [x] gofmt -l — clean on every touched file (two fixup commits for drift along the way)
    • [x] TDD-style tests for every task (failing test → implementation → passing test → commit)
    • [x] End-to-end round-trip test: upload → delete → re-upload → restore to the same row, library file back in place, archive cleared
    • [x] Final full-branch code review (superpowers:code-reviewer subagent) — one Important finding addressed inline in the last commit
    • [ ] Smoke test on fismen: push the built binary to the books container, trigger a sync, walk through delete + upload + archive-restore end-to-end. The existing Assassins Quest row should survive the schema migration untouched.

    Intended to be squash-merged. The 20 commits are a breadcrumb trail, not a curated history.

    🤖 Generated with Claude Code

    See on code.bas.es →

  • Summary

    Sets up everything for the next implementation pass — library curation (delete + upload) and the navigation redesign — but stops short of touching any real handlers or store methods. Two commits:

    1. Design system update (0307ac2) — reverses the earlier "there is no header, just a dropdown in the corner" decision. The new .site-header is a thin strip with three italic nav links on the left (shelf · library · errata, library as the default / route) and a + upload action on the right. Active-state is inked ink colour plus a thin stamp rule under the word. Mocks the inline delete-confirm button with both resting and armed states. Deletes the drag-and-drop dropzone concept we iterated on and then explicitly cut. Also fixes a small input bug: italic f was getting left-clipped in search inputs because the content box had zero inline padding — added 0.12em of inline-start padding across all text inputs. All changes live in design/ and are visible by opening design/index.html or design/preview/index.html in a browser. No backend code changes.

    2. Design spec (13ad7be) — docs/superpowers/specs/2026-04-11-curation-and-nav-design.md. Covers the three features landing in the next PR: delete books (soft delete, row kept, files moved to <data dir>/archived-books/<hash>.<ext>, auto-unshelve on delete, inline 2-click confirm UI on the book detail page), upload books (+ upload button in the site header triggers a file picker, new file runs through the same importer pipeline as the inbox watcher, re-uploading an archived book restores it), and the site header itself. Also covers an incidental internal/kobo fix: buildRemovedEntitlement currently synthesizes a fake UUID via uuidFromInt, which delete-of-a-shelved-book would silently trip over — the spec resolves it via a new GetBookByIDIncludingDeleted store method so the removal envelope carries the book's real UUID.

    The spec has a full self-review done; the open questions section is non-blocking.

    What's in this PR

    • Visual design for the three features, iterable in the browser at design/index.html and design/preview/*.html
    • Written design spec at docs/superpowers/specs/2026-04-11-curation-and-nav-design.md
    • No backend / store / importer / web handler changes — those come in the follow-up implementation PR

    Test plan

    • [x] design/preview/index.html opens in a browser, shows the new thin site header with + upload on the right, no dropzone wrapper
    • [x] design/preview/book.html shows the delete button in resting and armed states
    • [x] design/index.html component doc reflects the new header and no dropzone
    • [x] Search input in the library preview no longer clips the italic f
    • [ ] Spec review — read the spec and push back on anything that feels wrong before we write the implementation plan. Particularly worth a second look: the internal/kobo scope addition, the importer split of Import vs ImportFile, the flash cookie design, and the testing list

    Intended to be squash-merged; the two commits are a breadcrumb trail, not a curated history.

    🤖 Generated with Claude Code

    See on code.bas.es →

  • Summary

    When we last left this branch the Kobo was happily syncing metadata from books.fismen.no but quietly refusing to download any of the actual epubs — the device would fetch each book's metadata, think about it for half a second, and then move on without ever asking for the file. We'd been chasing half a dozen increasingly unlikely suspects for this (URL length, publisher sentinels, publication-date mapping, DRM fields, content-access endpoints) and had checkpointed the branch at 5882803 so we could start fresh.

    It turned out to be a one-line shape mismatch. Nickel parses /v1/library/{id}/metadata as a JSON array and reads index [0]; we were sending the bare metadata object, so the indexed read came up empty and DownloadContentFileCommand silently finished without ever issuing the GET to DownloadUrls[0].Url. calibre-web already wraps that endpoint in a one-element list — we just missed it because a dict-vs-dict diff between the two bodies stripped the outer array and showed zero key differences. Matching calibre-web fixes downloads end-to-end, confirmed on the device via the nc 192.168.86.39 5001 Qt debug stream: ResumingDownloader::startResumingDownloader::finished on /api/kobo/{token}/download/{id}/kepub within 700 ms of the metadata fetch.

    With the real root cause known, the branch also rolls back the pile of symptom-chasing workarounds from the earlier checkpoint:

    • Series metadata is back. Books with a series now show the series name, number, and a stable id on the device, matching calibre-web's shape exactly (Number and NumberFloat as JSON numbers, Id as a UUIDv3 in the DNS namespace keyed on the series name).
    • PublicationDate uses the book's added-at instant instead of a hardcoded 2020-01-01 stand-in.
    • Three dead download-route aliases and the content_access_book stub are deleted. Nickel only ever calls the one URL we emit in DownloadUrls, and it works.
    • A 140-line mimicCalibreWebSync diagnostic, a now-unused slugify helper, and several comments blaming the wrong fields are gone.
    • docs/kobo-api-findings.md is updated with the rediscovery trap so the next session doesn't fall into the same hole: the "What doesn't work" section becomes "The metadata-shape gotcha," and the status table shows downloads working.

    Also adds a new README.md (the repo didn't have one) with the minimum env vars needed to run the server and the one-line Kobo eReader.conf setup procedure for pointing a stock-firmware device at books — no SSH, no factory reset, no firmware modification.

    Net diff: ~271 lines deleted, ~200 added, most of the additions being the new README. The actual behaviour change is ~10 lines in internal/kobo/metadata.go and sync.go.

    Test plan

    • [x] Downloads verified end-to-end against Libra Colour firmware 4.45.23646 via the nc :5001 Qt debug stream.
    • [x] go build ./... && go vet ./... && go test ./internal/kobo/... all green.
    • [ ] Freshly factory-reset Kobo: paste the api_endpoint from the README into Kobo eReader.conf, reboot, confirm first-run sync pulls and downloads the whole shelf cleanly.

    Intended to be squash-merged; the individual commits on the branch are a breadcrumb trail, not a curated history.

    🤖 Generated with Claude Code

    See on code.bas.es →

  • Plan 3 of 4. The /api/kobo/{token}/v1/... HTTP surface that lets a stock-firmware Kobo Libra Color browse the shelf, download books, and push reading state. Wire shapes are taken verbatim from grimmory.

    What's in here

    • internal/kobo — Handler, requireToken middleware, snapshot builder + delta, sync token codec, Kobo 7-decimal timestamp codec, every JSON DTO, and one file per endpoint group
    • internal/store — kobo_sync_sessions CRUD with auto-supersede, reading_state CRUD with per-field LastModified merge, device_removals CRUD, GetBookByID
    • main.go — mounts the kobo router on the same ServeMux as the web router

    Endpoints

    • GET /v1/initialization (with x-kobo-apitoken: e30= header)
    • POST /v1/auth/device (random per-call tokens, explicit Content-Length)
    • GET /v1/library/sync (paginated 5/call, snapshot-based, x-kobo-synctoken round-trip)
    • GET /v1/library/{uuid}/metadata
    • GET /v1/library/{uuid}/state
    • PUT /v1/library/{uuid}/state (merge by per-sub-object LastModified)
    • DELETE /v1/library/{uuid} → device_removals
    • GET /v1/books/{uuid}/download
    • GET /v1/books/{uuid}/thumbnail/{w}/{h}/{greyscale}/image.jpg
    • POST /v1/analytics/gettests (mandatory)
    • POST /v1/analytics/event (mandatory)
    • GET /v1/products/{id}/nextread[/rest...] (mandatory)

    Out of scope (plan 4)

    • deploy.sh, systemd unit, Caddy config
    • Goroutine that calls DeleteOldKoboSyncSessions (function exists and is unit-tested, just not wired)
    • Web UI for clearing device_removals (minor follow-up)

    Fixes applied before PR

    • Superseded session guard in LibrarySync — a stale sync token for a superseded session now starts a fresh session rather than walking the stale one
    • nextread route now catches trailing sub-paths via {rest...} wildcard

    Test plan

    • [x] go test ./... -race clean across all 9 packages
    • [x] Hands-off bootstrap fails fast at OIDC discovery
    • [ ] Full smoke (you, after merge): paste the URL into your Libra Color's Kobo eReader.conf, trigger sync, confirm shelved books appear on the device, open one, let it read, confirm progress syncs back via /state

    Known sharp edges

    • Removed entitlements omit the BookMetadata block. Grimmory includes a minimal metadata block even for removals. The spec allows omission; real-device testing will confirm whether the Libra Color tolerates it.
    • nextread matches both the exact path and /nextread/{rest...} — if the device sends something stranger the catch-all returns {} per plan 2's design.

    Plan: docs/superpowers/plans/2026-04-11-kobo-sync.md

    See on code.bas.es →

  • Plan 2.5 of 4. Introduces the library/shelf distinction so the user can be deliberate about what's on the Kobo.

    What's in here

    • Schema: books.shelved_at INTEGER (nullable). Non-null = on the shelf. Edited into 001_initial.sql directly since nothing's deployed.
    • Store: ShelveBook, UnshelveBook, ShelvedBooks. All other SELECTs updated to include the new column.
    • /kobo page renamed to /shelf; the page now lists shelved books first, then the Kobo register/regenerate UI below.
    • Library list (/) shows every book and marks shelved ones with a small italic 'shelf' tag in oxblood.
    • Book detail page gains a 'Shelve →' (or 'Remove from shelf →') action.
    • Page-nav: library / shelf / errata.
    • New routes: POST /book/{uuid}/shelve, POST /book/{uuid}/unshelve. Both gated by RequireSession + RequireOrigin.
    • Spec updated.

    Out of scope

    • Plan 3: the Kobo sync API. The shelf is currently a flag that nothing reads — plan 3's snapshot algorithm will filter on `WHERE shelved_at IS NOT NULL`.

    Test plan

    • [x] go test ./... -race clean across all 8 packages
    • [x] Hands-off bootstrap: bogus OIDC issuer fails at discovery, shelved_at migration applied cleanly
    • [ ] Full smoke (you, after merge): drop a book, confirm it lands in / but not /shelf, click into book detail, hit Shelve, confirm it now shows on /shelf and the library list marks it

    Notes

    • Internal API paths keep /api/kobo/... (it's the Kobo's protocol, not ours). Only user-facing chrome talks about shelves.
    • Default for new imports: off the shelf. You drop a file, it lands in the library, you decide whether to shelve it.
    • Shelving doesn't bump updated_at — plan 3's snapshot diffing is presence-driven.

    Plan: docs/superpowers/plans/2026-04-11-shelf.md

    See on code.bas.es →

  • Plan 2 of 4. Adds the human-facing surface of the books server, the Reading Room visual design, and switches the project to self-hosted Newsreader fonts so books never contacts an external server at runtime.

    What's in here

    Design — Reading Room

    A literary, serif-only visual language. Single typeface (Newsreader) at every size and weight, ink on aged paper, no rectangular chrome, no buttons, no input boxes. Site nav is a single fixed dropdown in the top-right corner. Direction notes in design/specs/2026-04-10-reading-room.md; full design system at design/index.html with three preview pages under design/preview/. Newsreader fonts are self-hosted (six woff2 subsets, ~516 KB) and embedded into the binary.

    Auth

    • internal/auth — go-oidc/v3 verifier wrap, session lifecycle (create / lookup / sliding renewal / delete), RequireSession middleware, RequireOrigin CSRF guard
    • Sessions table CRUD with sliding 30-day expiry; auto-renewed by middleware when past half-life
    • Single allowed subject enforced both at exchange time and on every request
    • Session cookie HttpOnly + Secure + SameSite=Lax; OAuth state cookie matches

    Store additions

    • sessions CRUD (CreateSession, GetSession, RenewSession, DeleteSession, DeleteExpiredSessions)
    • devices CRUD (CreateDevice, ListDevices, GetDeviceByToken, GetDeviceByID, DeleteDevice, TouchDeviceLastSeen)
    • books additions: GetBookByUUID, SearchBooks (LIKE on title and author, case-insensitive), UpdateBookMetadata (preserves file_path / file_hash / added_at / uuid)

    Library additions

    • Rename and RenameCover for metadata-edit-driven on-disk moves; cleans up empty author directories

    Inbox additions

    • ListErrata + RetryErratum for the errata page; retry rejects path traversal

    Web

    • internal/web — Handler, Renderer, base layout + 5 page templates (library, book, edit, kobo, errata)
    • One file per page handler: library.go, book.go, edit.go, kobo.go, errata.go, auth.go, healthz.go (collapsed into handler.go)
    • Byline parser/formatter for the editorial 'Smith, Jones & Brown' format, roundtrip-tested
    • Cover and epub download served from disk via http.ServeFile, with safe Content-Disposition encoding
    • Background goroutine cleans expired sessions hourly

    main.go

    • Embeds design/ via //go:embed, serves at /static/
    • OIDC discovery at startup
    • Mounts HTTP on 127.0.0.1:8090
    • Signal-handled shutdown with 10-second drain
    • Inbox watcher and session cleanup as background goroutines

    Out of scope (plans 3-4)

    • /api/kobo/{token}/v1/* sync endpoints (the kobo page generates URLs that 404 until plan 3)
    • Snapshot algorithm and reading-state push/pull
    • Deployment script, systemd unit, Caddy config
    • Logout link in the page-nav (POST to /auth/logout works)
    • Cover thumbnails on the library list (text-only for now)
    • Templated error pages with incident IDs

    Test plan

    • [x] go test ./... clean across all 8 packages (config, store, library, epub, importer, inbox, auth, web)
    • [x] go test -race ./... clean
    • [x] Two startup error paths verified hands-off: missing config exits 1 fast, bogus OIDC issuer fails at discovery and exits 1
    • [ ] Full smoke test (you, after merge): set BOOKS_OIDC_* against pocket-id, run, log in, drop a real epub, browse the library, edit metadata, register a Kobo, drop junk into inbox, retry from /errata

    Known follow-ups (deferred)

    • /api/kobo/* — entirely plan 3
    • internal/store/devices.go: getDevice uses string concat for the WHERE column (constants only, no injection risk; cosmetic)
    • humanTime returns 'last week' for everything 7-30 days old (cosmetic)
    • Auth.Store() accessor is unused (dead code, harmless)

    Plan: docs/superpowers/plans/2026-04-10-webui-auth.md Design spec: design/specs/2026-04-10-reading-room.md

    See on code.bas.es →

2026-04-10
  • First of four plans implementing the books server design at docs/superpowers/specs/2026-04-10-books-server-design.md. Delivers the foundation: a books Go binary that watches an inbox directory, parses dropped epubs, persists metadata in SQLite, and files them into a managed library tree under <author>/<title>.epub. No HTTP, no auth, no Kobo — those come in plans 2–4.

    What's in here

    • internal/config/ — env-based config loader (BOOKS_DATA_DIR plus three path overrides)
    • internal/store/ — SQLite store with embedded migrations. Full 11-table schema applied up front (most tables empty until later plans). Typed CRUD for books and authors. MaxOpenConns(1) so the foreign_keys pragma actually applies to every query.
    • internal/library/ — sanitised path components (handles /, \, :, control chars, whitespace, unicode), Place/WriteCover/Remove with os.Rename + EXDEV-style copy fallback. Atomic via .tmp rename.
    • internal/epub/ — pure functional OPF parser. Bytes in via io.ReaderAt, Metadata struct + cover bytes out. Supports both EPUB2 (<meta name=\"cover\">) and EPUB3 (properties=\"cover-image\") cover declarations.
    • internal/importer/ — orchestrates parse → upsert → place → set file path. Duplicate detection by file_hash runs before upsert so re-dropped files are deleted, not re-placed. Failed imports quarantined to inbox/.failed/<name> with sidecar .err.
    • internal/inbox/ — startup scanner (filepath.WalkDir, skips .failed/ and hidden) + fsnotify watcher with size-stability debouncing.
    • main.go — wires everything, signal handling, slog logging.

    Out of scope (plans 2–4)

    • Web UI, OIDC, sessions
    • Device tokens, Kobo sync API
    • Deployment script, systemd unit, Caddy config

    Test plan

    • [x] go test ./... and go test -race ./... clean across all packages
    • [x] Manual smoke: drop a non-zip → moves to .failed/ with sidecar .err, server stays up
    • [x] Manual smoke: drop a constructed epub ("The Smoke Test" / "Anne Author") → lands at library/Anne Author/The Smoke Test.epub

    Known follow-ups (deferred to plan 2)

    • N+1 in ListBooks flagged with TODO(plan-2)
    • No tests for internal/inbox/watcher.go (fsnotify timing is fiddly)
    • PRAGMA foreign_keys=1 is on but the junction tables lack REFERENCES clauses
    • Orphan rows in authors when a book's author list is replaced

    Plan: docs/superpowers/plans/2026-04-10-foundation.md.

    See on code.bas.es →