Local-only Wave configuration sync:
- Drop one-shot contract schemas no longer referenced by any
pipeline.
- Replace gitea/github issue-impl pipelines with the unified
refresh/research/rewrite/scope set; add bb (bitbucket) and gl
(gitlab) variants.
- Add scoper persona and matching scope contracts.
- Update existing personas and pipelines to current style.
No effect on the librenotes runtime; this only touches the
.wave/ tooling directory used by the local dev harness.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CHANGELOG.md follows Keep-a-Changelog. The Unreleased section
sits at the top; the v0.1.0 entry summarises every commit on
the path from the empty fork to a hosted multi-tenant build:
fork + restructure, storage/auth/tenant/httpapi packages, the
serve command, full frontend (landing, login, verify, app shell,
PWA, sync), Docker + deploy + backup tooling, docs, and the
community infrastructure. Two known-incomplete items called out
explicitly: the upstream Notesium UI is not yet wired into the
multi-tenant shell, and SMTP delivery has only been exercised
against the LogMailer.
docs/launch-checklist.md is the manual QA pass for v0.1.0:
environment prerequisites, the full sign-up to first-note flow
on Chrome / Firefox / iOS Safari / Android Chrome (with explicit
PWA-install and offline-shell verifications), documentation
accuracy spot-checks, the community-infra render checks, and a
backup-and-restore round-trip on the staging host. The QA
performer signs off at the bottom; failures are filed as
separate bugs against the milestone.
The actual v0.1.0 tag will be cut by a maintainer; tagging is
not part of this commit.
Refs #30 and #33.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmd/librenotes/web/public/onboarding.js triggers after a session
is verified and runs only when the tenant-scoped key
"onboarded" is unset.
Behaviour:
- Reads the dismissal flag from authClient.tenantStore() so each
tenant's state is isolated and survives logout-then-login on
the same device only if they're the same user.
- Lists /api/notes; if the notebook is empty, PUTs a sample
"welcome" note so a brand-new user has somewhere to land. We
don't seed when notes already exist (covers signing in on a
second device for the first time).
- Opens app.html's <dialog id="onboarding-dialog"> with showModal
and persists "onboarded": true on submit so returning users
never see it again.
- Seed failures are logged but do not block the dialog —
onboarding shouldn't depend on a successful network round
trip. The dialog just won't have a sample note to point at.
Dialog content covers the four user-guide concepts: notes-as-
markdown, [[wikilinks]], offline-first sync, tenant isolation.
Service worker precaches onboarding.js so the dialog is also
available to offline-first returning visitors.
Closes#32.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gitea issue templates (.gitea/issue_template/):
- bug.yml: structured form requiring version, environment,
what-happened, repro steps, expected, optional logs. Routes
security reports to security@librete.ch instead of public
issues.
- feature.yml: prompts for the underlying problem before the
proposed solution, plus alternatives and out-of-scope.
Pull request template (.gitea/PULL_REQUEST_TEMPLATE.md):
checklist for tests, lint, manual exercise, docs, and changelog.
Asks for explicit reviewer notes so trade-offs surface in the
PR description rather than being lost in chat.
CODE_OF_CONDUCT.md: links to Contributor Covenant 2.1 verbatim
rather than inlining; documents scope, reporting address
(conduct@librete.ch), and points enforcement at the Covenant's
own Enforcement Guidelines.
README links the docs/ tree, CONTRIBUTING, and the CoC so new
contributors find the entry points.
Closes#31.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
docs/user-guide.md — magic-link sign-in, note basics, wikilink
syntax, keyboard shortcuts, offline behaviour, and privacy
notes (sessionStorage for tokens, tenant-scoped localStorage).
docs/self-hosting.md — system requirements, Docker Compose
quick-start, the full LIBRENOTES_* env-var matrix (which are
required, which conditional), reverse proxy snippets for Caddy
and nginx, volume layout, the in-binary healthcheck, and
update/rollback procedure.
docs/api.md — every public endpoint: auth (login/verify),
notes CRUD, /api/whoami, /healthz. Status codes per endpoint,
the optimistic-locking ?base=<unix> contract for PUT/DELETE,
note-ID regex, and the rate-limit policy.
CONTRIBUTING.md — dev setup (Nix flake .#dev, plain Go, Docker),
package layout overview, coding standards (one-way dep flow,
tenant FS gateway requirement), branch naming, commit format,
and the PR process. Also points security reports at
security@librete.ch rather than public issues.
Closes#29.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI deployment (.gitea/workflows/deploy.yml):
- Two jobs (build, deploy) gated on the repo variable
DEPLOY_ENABLED=true so the workflow exists but does nothing
until secrets and host are configured.
- Build pushes two image tags per run: rolling :main + the short
SHA on main, or vX.Y.Z + :latest on tag pushes. Immutable per
commit/tag tags make rollback trivial.
- Deploy SSHes to DEPLOY_HOST, runs docker compose pull && up -d
in DEPLOY_PATH, then polls HEALTH_URL for up to a minute. A
failed health check fails the workflow, which is the alert.
- Required secrets and the rollback procedure are documented in
docs/operations.md.
Backup tooling (scripts/):
- backup.sh: SQLite online .backup snapshot + tarball of the
per-tenant data dir + info.txt header, all wrapped into a
single librenotes-YYYYMMDD-HHMMSS.tar.gz. Optional BACKUP_REMOTE
triggers an rclone copy for off-site storage.
- backup-prune.sh: enforces retention "30 daily + 12 monthly".
Sorts archives by filename (date is in the name so lex order
matches chronological) and keeps the newest 30 plus the newest
archive for each of the most recent 12 months.
- backup-restore-test.sh: extracts the most recent archive into
a tmpdir, runs sqlite3 .schema (proves DB readability), and
asserts the notes tar has at least one entry. Failure is the
alert. Wired into a separate weekly timer.
- librenotes-backup.{service,timer}: systemd units for the daily
03:17 UTC run with 5min jitter; ProtectSystem=strict, only
/var/backups/librenotes is writable.
- librenotes-backup-verify.{service,timer}: weekly Monday
04:00 UTC restore test.
Closes#26 and #27.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dockerfile is multi-stage:
- build: golang:1.25-bookworm, CGO_ENABLED=0 (modernc.org/sqlite
is pure-Go) + -trimpath + -ldflags "-s -w" so the resulting
binary is small and reproducible-ish.
- runtime: gcr.io/distroless/static:nonroot, ~2 MB. Runs as uid
65532. /data and /var/lib/librenotes are declared volumes so
per-tenant notes and the SQLite database survive container
restarts.
healthcheck subcommand: distroless static has no shell or
wget/curl, so /healthz is reachable but no client to call it. A
new "librenotes healthcheck" subcommand uses net/http to GET
$LIBRENOTES_HEALTHCHECK_URL (default 127.0.0.1:8080/healthz) and
exits non-zero on failure. Both compose files invoke it from the
HEALTHCHECK directive.
httpapi adds a tiny GET /healthz that returns {"status":"ok"}
(no DB ping yet — added when readiness probes need it).
docker-compose.yml: dev stack on :8080 with named volumes and a
LogMailer; everything via env vars, JWT secret defaulted to a
dev value.
docker-compose.prod.yml: layered overrides — pulls a registry
image, expects LIBRENOTES_* env, sets memory limits and JSON-
file log rotation.
Closes#25.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmd/librenotes/web/public/sync.js drives the offline-online
reconciliation flow against the notes REST API:
- start(): registers online/offline window listeners, runs an
initial syncOnce() if currently online.
- syncOnce(): push() then pull(); emits "librenotes:sync-state"
events with state in {online, offline, syncing, synced, error}.
- push(): walks notesCache.pending() (rows with dirty=1, including
tombstones). PUTs use ?base=<synced_at> for optimistic locking
and DELETEs use the same. The notes API returns 409 with the
current server body on conflict; sync.js stashes the pair in
conflictsById and dispatches "librenotes:sync-conflict" so the
app shell can render a resolution dialog.
- pull(): GETs the summary list, refetches any row whose server
updated_at exceeds the local synced_at (or that is missing
locally), and stamps it as cleanly synced. Skips locally-dirty
rows so push's conflict path stays authoritative.
- resolveConflict(id, "local"|"remote"|"merge", merged): replays
the user's choice. "local" and "merge" PUT with the latest
server base so the second attempt accepts; "remote" overwrites
the local cache with the server copy.
app.html now includes a sync-state badge in the header and a
<dialog> for conflict resolution wired to the events. app.js
calls notesSync.start() on load and routes dialog clicks back to
resolveConflict. The dialog uses native <dialog>.showModal(),
which all current target browsers support.
style.css adds badge colour states (syncing/synced/offline/error)
and a two-column conflict layout that collapses on narrow widths.
Closes#23.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmd/librenotes/web/public/notes-cache.js exposes window.notesCache
with the offline-first read/write API the rest of the app uses:
Schema (object store "notes", key path "id"):
{ id, title, content, updated_at, synced_at, dirty, deleted }
plus a denormalised dirty_idx:0|1 column because IndexedDB cannot
index booleans directly. Two indexes — by_updated_at for sorted
listing, by_dirty for the sync controller's pending-queue scan.
Per-tenant database name "librenotes-notes-{user_id}" so two
users on the same browser have fully separate offline caches and
clearAll() (called from authClient.clearSession on logout) drops
only the leaving user's data.
Public surface:
- list/get/put/remove: straight CRUD.
- markDirty(id, patch): stage an offline edit. Bumps updated_at
to now() but preserves synced_at so the sync controller can
detect server-side concurrent edits via the ?base=<unix> 409.
- markDeleted(id): tombstone (deleted:true, dirty:true) so the
sync controller can replay the delete on reconnect.
- markSynced(id, serverUpdatedAt): clear dirty + record
serverUpdatedAt as synced_at; if tombstone, drop entirely.
- pending(): returns dirty rows for the sync queue.
- estimateUsage(): wraps navigator.storage.estimate so the UI
can warn before quota.
Quota errors are remapped to a typed err.code === "QUOTA" so the
UI can show "out of space" instead of a generic failure.
The service worker precache list now includes notes-cache.js and
sync.js so the offline shell has the cache layer too.
Closes#22.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
internal/httpapi/notes.go exposes:
- GET /api/notes list summaries {id, title, updated_at}
- GET /api/notes/{id} full {id, title, content, updated_at}
- PUT /api/notes/{id} create/update; ?base=<unix> for
optimistic-locking conflict detection
- DELETE /api/notes/{id} remove; ?base=<unix> guards against
deleting a row modified after the
client last saw it
Backed by tenant.FS so all reads/writes go through the per-user
sandbox — path traversal is rejected at parse time (regex slug)
and again by os.Root inside the FS layer.
On-disk format is plain Markdown: first line `# Title`, rest is
content. grep / cat / vim still produce a usable view of raw
files. Title round-trips through composeNote/splitTitle.
Conflict semantics: when the client supplies ?base=<unix>, the
server compares against the file's mtime. If the file is newer,
respond 409 with the current note body so the client can present
a merge UI. Same logic on DELETE returns 409 alone.
cmd/librenotes/serve.go grows a tenantPool that memoises FS
handles per user id; defer-closes them on shutdown.
Tests cover: full CRUD round-trip, cross-tenant isolation,
unauthenticated 401s, invalid IDs (regex rejection), and the
conflict path with a real mtime advance.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
internal/notesium/web/app/pane.js drove the sidebar/pane resize
via mousedown + window-level mousemove/mouseup, which doesn't
fire on touch (browsers only emulate mouse for taps, not drags).
Replaced with pointer events:
- @pointerdown on the handle (covers mouse, touch, pen).
- setPointerCapture so we keep receiving pointermove/pointerup
events when the pointer drifts off the handle. This eliminates
the need for document-level listeners and avoids stuck-drag
states when the user releases outside the window.
- pointermove + pointerup + pointercancel listeners on the
captured target only — when the capture ends they're removed
regardless of whether the user is still on top of the handle.
- Filter on event.pointerId so a second simultaneous touch
(e.g., a multi-finger gesture) cannot hijack the in-progress
resize.
- event.button !== 0 guard rejects right-click / middle-click.
- touch-action: none on the handle so the browser doesn't try
to interpret a horizontal drag as a page scroll.
CodeMirror's internal mousedown handlers in note.js / preview.js
are left alone — those are link-click guards, not drags, and
CodeMirror's own pointer support handles touch internally.
Closes#20.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
style.css now has explicit breakpoints and primitives covering
the full target range:
- Global: overflow-x: hidden on body, max-width:100% on media,
fluid typography via clamp() so headings shrink on 320px.
- .wrap: 64rem cap at desktop, 72rem at 1440px, 96rem at 2560px;
generous side padding at large widths so text doesn't hug the
edge on huge monitors.
- .app-shell layout primitive (grid: sidebar + content [+ aside
on ultrawide]) ready for the eventual notes UI:
* mobile: single column, sidebar hidden behind a toggle
([data-sidebar="open"] reveals it).
* 768px+: 2-column with 16rem sidebar.
* 1024px+: 18rem sidebar.
* 1440px+: 20rem sidebar, content max-width 56rem so reading
lines don't grow unbounded.
* 2560px+: 3-column (sidebar | content | aside) so the
editor stays at reading width while the extra real estate
hosts backlinks/preview.
- .app-resize-handle with touch-action:none so pointer-event
drag handlers won't conflict with browser scrolling.
- Auth card tightens on viewports under 360px.
Result: no horizontal scroll at any width; content uses ultrawide
space effectively without sacrificing legibility.
Closes#18.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- manifest.webmanifest: standalone display mode, theme #2563eb,
start_url=/app.html (so users who install land in the app
shell, not the marketing page), scope=/. Three icons: 192px
any-purpose, 512px any-purpose, 512px maskable for adaptive
icons on Android.
- icons/: PNGs generated from favicon.svg.
- sw.js: cache-first for the precached app shell, network-first
for /api/* and /auth/* (we never serve stale auth or notes).
Versioned cache name (librenotes-shell-v1) so a SW update
evicts old assets. skipWaiting + clients.claim so a new SW
takes over without a manual reload.
- pwa.js: registers the SW on every page and handles
beforeinstallprompt by showing #install-btn. Hides the button
again on appinstalled. Defer loaded so it never blocks render.
- All HTML pages link the manifest, set the theme-color meta,
and load pwa.js. Landing page exposes the install button next
to the existing CTAs.
Closes#19.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmd/librenotes/web/public/auth-client.js exposes window.authClient
with the full session API used by the rest of the frontend:
Session storage (#14):
- saveSession / loadSession / clearSession / isAuthenticated
- Backed by sessionStorage, not localStorage: tokens are isolated
per tab and cleared on tab close. localStorage would survive
tab close on a shared device, which we want to avoid.
- loadSession returns null when expires_at has passed, so callers
treat expired sessions as logged-out without a network round
trip.
API wrapper (#14):
- apiFetch(url, init) attaches Authorization: Bearer <jwt> to
every call. On 401 it clears the session and redirects to
/login.html?next=<current-path> so the user returns where they
started. Throws after the redirect so the caller's .then does
not run with stale data.
Tenant-scoped localStorage (#15):
- tenantStore() returns a get/set/remove wrapper whose keys are
prefixed "librenotes:{user_id}:". Two users on the same browser
therefore have fully independent UI state. JSON serialisation
with try/catch fallbacks for corrupted or quota-exceeded
storage so a bad blob never crashes the app.
- clearTenantStore(userID) removes every key with that prefix.
Called from clearSession() so logout wipes both the JWT and
the user's preferences.
verify.html + verify.js complete the magic-link flow: read
?token=, POST /auth/verify, hand the response to saveSession(),
strip the token from the URL via history.replaceState. Errors
route the user back to /login.html.
app.html + app.js are a minimal authenticated landing demonstrating
the full stack end-to-end: apiFetch hits /api/whoami, tenantStore
persists a theme preference, logout clears both. The full notes
UI is left to a later phase — this is the seam.
Closes#14 and #15.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmd/librenotes/web/public/login.{html,js}:
- Email input with required + autocomplete + autofocus, ARIA
attributes for screen readers (aria-describedby, aria-invalid,
role="alert" on the error container, role="status" on success).
- Client-side regex validation runs before POST to /auth/login
to avoid a network round-trip for obvious typos. Server is
still the source of truth.
- Loading state disables the button and changes its label.
- Success state replaces the form with "Check your email"
including the address, plus the 15-minute / single-use note.
- Error states map server statuses to user-friendly messages:
429 -> "too many requests", 400 -> "invalid email", anything
else -> generic server error. Network errors get their own
message so users can distinguish offline from server problems.
- No external CSS or JS dependencies; works with keyboard and
on small viewports.
Closes#13.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cmd/librenotes/serve.go wires the multi-tenant HTTP server:
storage + auth + httpapi packages, configurable via flags or
LIBRENOTES_* env vars. Embeds web/public/ for unauthenticated
static content. Generates an ephemeral JWT secret with a warning
when none is supplied. Adds security headers (CSP, nosniff,
DENY-frame, no-referrer) on every response. Background goroutine
purges expired magic-link tokens every 10 minutes.
cmd/librenotes/web/public/ provides the unauthenticated frontend:
- index.html: hero, features grid, fork attribution, footer.
Mobile-first, responsive from 320px up via clamp() and
auto-fit grid. SEO + Open Graph tags. No JS dependency.
- privacy.html: placeholder privacy policy (full text TBD).
- style.css: shared design tokens (light/dark via [data-theme]),
used by landing, auth pages, and the post-login app shell.
- favicon.svg: minimal mark.
The "serve" command sits alongside the original notesium CLI
verbs; main.go dispatches "serve" to the new code path and
forwards everything else to notesium.Run().
Closes#16.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
internal/httpapi/ provides:
- Tenant{UserID, Email} carried on context.Context, with
WithTenant / TenantFrom helpers and ErrNoTenant for the
programming-error case (route reached without middleware).
- AuthMiddleware verifies an Authorization: Bearer <jwt> on every
request via auth.Signer.Verify (which already enforces HS256
and rejects alg=none). On failure: 401, with the underlying
reason logged server-side but not exposed to the client.
- RequireTenantOwnership(ownerID) compares the request's tenant
against the resource owner; returns 403 on mismatch. Handlers
that touch tenant-owned resources call this guard.
- Server.Routes() mounts /auth/* unauthenticated and wraps
/api/* with the middleware. /api/whoami is included as the
canonical example of a tenant-scoped endpoint.
Tests cover: valid JWT pass-through, missing/empty Authorization,
wrong scheme, malformed JWT, tampered signature, JWT signed with
a different secret (cross-tenant key confusion), and the 200/403
matrix for RequireTenantOwnership.
Closes#11.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
internal/tenant/ provides FS, a sandboxed handle for a single
tenant's notes directory. Implementation strategy:
- Defence in depth: every relative path is validated up front
(rejects "..", absolute paths, NUL bytes, empty), then handed
to os.Root (Go 1.24+) which enforces the boundary at the
syscall layer using openat(2)+RESOLVE_BENEATH on Linux. This
closes TOCTOU races and symlink-target swapping.
- WriteFile is atomic (write to .tmp, rename in-root). Mode 0o600
on files, 0o700 on directories. Tenant root is created with
0o700 by Open().
- Errors are normalised: fs.ErrNotExist -> ErrNotFound, anything
os.Root rejects as "outside" the root -> ErrInvalidPath. The
HTTP layer can map cleanly to 404 / 400.
Tests cover the full traversal attack surface — "../", absolute
paths, mixed separators, NUL bytes, "." and "" — plus symlink
escapes and cross-tenant isolation. All vectors return errors;
none escape the root.
Closes#10.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
internal/auth/ provides:
- TokenStore: 32-byte cryptographically random one-time tokens.
Only the SHA-256 hash is persisted (so a DB leak doesn't grant
active sessions). Comparison uses subtle.ConstantTimeCompare.
Single-use is enforced via UPDATE ... WHERE used_at IS NULL.
- Signer: HS256 JWTs with 24h lifetime, jwt.WithValidMethods to
reject alg=none and other downgrade attacks.
- LogMailer (dev) and SMTPMailer (prod via net/smtp) behind a
Mailer interface.
- RateLimiter: DB-backed fixed window per email; default 5 per
15 min for the magic-link flow.
- Service: orchestrates RequestLogin (auto-creates user on first
login, generates token, emails magic link) and Verify (consumes
token, updates last_login, issues JWT).
- Handlers: POST /auth/login and GET/POST /auth/verify.
HandleLogin returns 202 even on validation failure to avoid
account enumeration; rate-limit hits surface as 429.
Schema additions: magic_tokens (with FK + cascade) and
login_attempts. UserStore.SetStoragePath added for completeness.
Tests cover: token issue/consume, single-use, expiry, rate limit,
JWT round-trip, alg=none rejection, signature tampering, purge,
HTTP handlers (login + verify, missing/invalid token paths).
Closes#9.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
internal/storage/ provides:
- Open(path) to create or open the SQLite database with WAL journal,
busy timeout, and foreign keys enabled
- Embedded migrations that create the users table on first run
- UserStore with Create, GetByID, GetByEmail, UpdateLastLogin, Delete
- Email normalisation (trim+lowercase) and uniqueness enforcement
with ErrEmailTaken
- ErrNotFound on lookups and deletes
- UUIDv4 IDs auto-generated when caller leaves ID empty
Uses modernc.org/sqlite (pure-Go) so the binary stays CGO-free and
matches Dockerfile.dev's CGO_ENABLED=0.
Tests cover all CRUD operations, email uniqueness (case-insensitive),
WAL mode verification, and ErrNotFound paths.
Closes#8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- flake.nix: rebrand description, add Go 1.25, gopls, gotools,
staticcheck, golangci-lint, gnumake to all dev shells. Add a
plain `dev` shell (`nix develop .#dev`) that does not wrap the
shell in the bubblewrap sandbox so contributors can use a
standard Go toolchain.
- Dockerfile.dev: golang:1.22-bookworm with make, git, gopls and
staticcheck, /workspace as default cwd. CGO disabled.
- README: document both nix and Docker dev paths.
flake.lock is committed for reproducibility.
Closes#6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runs on push to main and pull requests against main:
- go mod download + verify
- make lint (go vet)
- make build
- make test (race detector)
Uses actions/setup-go@v5 with built-in module caching, Go 1.22.
Workflow times out at 5 minutes per the acceptance criteria.
Closes#5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Standard targets:
- build: compiles cmd/librenotes with version/buildtime ldflags
- test: race detector enabled, full module
- lint: go vet, plus staticcheck if available
- run: build + execute, ARGS forwarded
- clean: remove binary and test/coverage artifacts
Variables (BINARY, OUTDIR, GO, GOFLAGS, LDFLAGS, TESTFLAGS) are
overridable so the CI workflow (#5) can invoke targets with
custom output paths or flags.
Closes#38.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the upstream Notesium README with librenotes-specific
content: project description, multi-tenant goals, build/run
instructions referencing cmd/librenotes, Nix-based dev setup,
fork attribution, and MIT license note.
CI badge points at the workflow that #5 will create. Module path
and directory layout match the structure landed in the previous
fork commit.
Closes#37.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LICENSE retains the original Notesium copyright alongside librenotes.
NOTICE records the upstream URL, fork commit hash
(aff9f460c2d864112db7f0935b4168b107289d91), fork date, and
instructions for contributors who want to add the upstream remote
and cherry-pick patches.
Closes#36.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Initial fork of github.com/alonswartz/notesium into librenotes:
- Source moved to internal/notesium/ (package notesium)
- Thin entry point at cmd/librenotes/main.go
- Module renamed to git.librete.ch/public/librenotes
- main() exposed as notesium.Run()
- LICENSE preserved (MIT), NOTICE added with attribution
- Web assets and completion.bash co-located with embedding code
to satisfy go:embed path constraints
Closes#3, #34, #35.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove redundant --login librete flags from all gt-* pipeline tea commands since
authentication is already configured via tea logins. This simplifies the commands
and prevents potential authentication issues.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
gt-issue-impl, gt-issue-research, gt-issue-rewrite, gt-issue-update
pipelines with corresponding prompts. Mirrors the gh-issue-* variants
but uses tea CLI with --login librete for Gitea authentication.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
gh-issue-impl, gh-issue-research, gh-issue-rewrite, gh-issue-update
pipelines with corresponding prompts for fetch-assess, plan,
implement, and create-pr steps.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Analyst, commenter, and enhancer personas for Gitea issue
pipelines via the tea CLI with --login librete auth.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
JSON Schema definitions for all pipeline handover contracts
including issue analysis, research, enhancement, and sync flows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Nix devshell with gh, bubblewrap sandbox, and yolo mode.
Gitignore for .claude, .wave internals, secrets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>