Commit graph

67 commits

Author SHA1 Message Date
c2a354809a v0.7.1: in-app password change, custom labels, always-on folders
- Settings -> Account: change your own password (current + new), session
  preserved via update_session_auth_hash.
- Custom labels: per-user palette in Settings -> Labels; apply/remove from a
  message's ... menu (menu stays open, checkmark = applied). Labels are unified
  with tags; chip colours sync to the global TagColor. New Label model +
  migration 0017; POST /a/{id}/label (session, owner-scoped, palette-validated).
- Sidebar Keep/Deleted folders are now always shown; count badge only when
  non-empty. Updates the stale 'hide when empty' note in CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 15:19:54 +01:00
fcd7da853f docs: say 'API key' instead of 'token' in agent-facing wording
Sweep human-facing 'personal API token' / '<your-personal-token>'
placeholders to 'API key' / '<your-personal-key>' across the API docs,
quickstart, deployment guide, LLM helper, and posting skill, matching the
in-app 'Settings -> API Keys' naming. The AGENTFEED_TOKEN env var name and
HTTP 'bearer token' terminology are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 23:23:53 +01:00
89936ebf58 v0.7.0: mark all as read by clicking the unread badge
- Click a source's unread count in the sidebar to mark all of that source's
  messages read (clears the badge + reduces the All-sources total); click the
  All-sources total to mark everything read.
- POST /feed/mark-read (session, owner-scoped, excludes trashed). Visible feed
  rows drop their unread state too. Badge is keyboard-activatable (Enter/Space).

156 tests pass (4 new); inline JS re-validated with node --check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 22:26:48 +01:00
7b161f1276 v0.6.3: UI wording -> "messages"; folder spacing + tint
- User-facing "artifact(s)" -> "messages" across the UI (feed count, empty
  states, preview, About, Storage). Model/API/skill keep "artifact" internally
  — no agent-contract change.
- Sidebar: Keep/Deleted folders get spacing between them and share the "All
  sources" bold + blue-tint styling (special folders).
- Fix: About page points to Settings -> API Keys (was stale "Settings ->
  Account / API token").

152 tests pass; inline JS re-validated with node --check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 21:37:08 +01:00
c1be2face7 v0.6.2: order sources by most recent message (recency)
- Sidebar sources ordered by Max(created_at) desc instead of alphabetically;
  "All sources" stays pinned at top (rendered separately).
- Live updates move a source to the top when it gets a new message, so the
  recency order holds without a reload.

152 tests pass; JS re-validated with node --check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 21:18:49 +01:00
ab3e92268a v0.6.1: pin Keep/Deleted folders to sidebar bottom; highlight All sources
- Move the Keep/Deleted system folders out of the scrolling source list into a
  pinned section at the bottom of the sidebar (only sources scroll now), so the
  folders stay reachable with many sources. Renamed .source-folders ->
  .system-folders; .source-list flexes to fill.
- "All sources" is visually distinct (bold + subtle blue tint) as the primary
  entry.

Template/CSS only. Feed/trash rendering tests pass; feed renders in all views.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 21:07:39 +01:00
548ba3a669 v0.6.0: per-artifact actions menu (⋯ + right-click)
A ⋯ button on row hover and right-click on a feed row open the same floating
menu: Keep / Open / Copy link / Delete. Dismisses on click-away/Escape,
edge-flips to stay on-screen. Standalone one-click Delete icon stays; the
Trash view keeps its dedicated Undelete.

- Row markup: actions grouped in a .row-actions container (delete + ⋯ in the
  feed/keep views; undelete in trash). Reuses the existing keep endpoint.
- Menu only in the normal/keep views, not trash. Keep-toggle syncs the row's
  kept badge + the preview Keep button, and (in the Keep folder) drops the row
  when unkept.
- Frontend-only (no backend change); inline JS validated with node --check;
  152 tests pass; feed renders in all views.

Share + Add-label join the menu in later slices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 13:12:27 +01:00
57ad58ff2d v0.5.1: automatic Trash purge (cron-less background thread)
Trashed artifacts past a ~3-day retention window are now purged automatically.

- storage.purge_expired_trash(): removes trash older than TRASH_RETENTION
  (all users), tolerant of already-gone rows (concurrent-safe).
- spawn_trash_purge(): runs it on a daemon thread (manages/closes its own DB
  connection), triggered by each delete so it's cron-less and off the request
  thread. Inline fallback when AGENTFEED_ASYNC_PURGE is off (tests).
- Wired into both delete paths (API + session). Manual Empty Trash unchanged.
- Tests: purge removes expired-only; delete triggers the purge. 152 passing;
  live delete verified with the thread enabled (204, server healthy).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 13:01:12 +01:00
4254164bef v0.5.0: Trash (soft-delete) + Keep folder
Delete now moves an artifact to a recoverable Trash instead of hard-removing.

- Model: Artifact.deleted_at (nullable, indexed) = in Trash.
- Soft-delete everywhere: DELETE /api/artifacts/{id} sets deleted_at
  (idempotent 204); session POST /a/{id}/delete for the feed's hover Delete
  icon; POST /a/{id}/undelete restores; POST /feed/empty-trash purges.
- Trashed artifacts are off-quota (deleting frees quota) and eviction-protected
  (never evicted early); hidden from feed, sources, counts, and all API reads
  (list/detail/raw).
- Sidebar: "Deleted" and "Keep" system folders at the bottom (counts, hidden
  when empty); ?folder=deleted / ?folder=keep views reuse the row rendering.
  Deleted view has hover Undelete + an Empty Trash button. Keep = view over
  is_kept. Live-poll disabled in folder views.
- Docs (api.md delete section, CLAUDE.md), 10 new/updated tests. 150 passing;
  API soft-delete lifecycle verified live.

Purge is manual (Empty Trash) this release; auto purge-on-delete (background,
~3-day retention) is the planned fast-follow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-07 12:48:43 +01:00
6c1823e65e v0.4.1: live updates also refresh the source sidebar
Extends v0.4.0 without a second request: the sidebar is updated client-side
from the rows the existing /feed/updates poll already returns.

- Each new row's data-source drives the sidebar: per-source + "All sources"
  unread counts bump; a first-time source is inserted into the list
  (alphabetically). Reuses the existing count-badge pattern.
- sourceLinks is now live-queried (was cached at load) so sources added live
  stay keyboard-navigable.
- No endpoint/model change. 142 tests pass; feed renders.

Caveat: while filtered to a single source, other new sources appear only on
reload (the poll is scoped to the selected view).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-05 14:37:45 +01:00
cae414c307 v0.4.0: live feed updates (auto-insert new posts)
New artifacts appear at the top of the feed without a manual refresh.

- GET /feed/updates (session-authed): new artifacts since a timestamp marker,
  owner-scoped and filter-aware (source/label/kind/q), returned as row HTML.
- Feed JS polls every 5s, paused while the tab is hidden (immediate poll on
  refocus). Prepends new rows into today's group without touching the selected
  row or the preview iframe — what you're reading stays put and selected.
- Fresh rows show "now", aging into their real time after ~3 min; brief
  highlight on arrival.
- Extracted _artifact_rows.html partial, shared by the day-group render and the
  updates endpoint, so live rows match exactly.
- Polling chosen over SSE/WebSockets: zero infra on sync-WSGI + SQLite (see
  planning/next_steps for the SSE upgrade path). 142 tests pass; feed page +
  endpoint verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 23:02:07 +01:00
f6f004c689 v0.3.2: add GET /api/artifacts/{id}/raw for content read-back
Metadata (GET /api/artifacts/{id}) stays metadata-only; the full content now
has its own endpoint so an agent can verify exactly what it posted.

- New ArtifactRawView at /api/artifacts/{id}/raw: bearer-or-session auth,
  owner-scoped. Serves exact source text (markdown/html_full/json/csv), file
  bytes (image/pdf/binary), or 404 for render-only kinds (html_fragment/deck).
  Reuses raw_artifact_path/safe_artifact_path.
- serialize_artifact exposes api_raw_url; /api/capabilities advertises the
  route. (The old /a/{id}?raw= viewer stays session-only for browsers.)
- Docs + tests (exact content, owner-scoping, 403 unauth, 404 render-only).
  Suite: 136 passing; verified live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 15:20:37 +01:00
2b901f7844 v0.3.1: bearer-token reads; public skill + capabilities
Fixes an inconsistency an agent hit: it could POST/PATCH/DELETE with its token
but GET (readback) required a session and 302-redirected to the browser login.

- GET /api/artifacts and GET /api/artifacts/{id} now accept a bearer token OR
  a session, owner-scoped (a token reads only its owner's artifacts). Agents
  can read back what they posted with the same token.
- Unauthenticated API requests return a JSON 403, never a browser-login
  redirect (resolve_request_user helper; dropped @login_required on the JSON
  read views).
- GET /api/skill and GET /api/capabilities are now public (no user data), so
  an agent can fetch the contract + posting instructions before it has a key.
- Docs (api.md, CLAUDE.md) updated; tests for bearer readback, owner-scoping,
  and the JSON-403-not-redirect behavior. Suite: 132 passing; verified live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 14:50:48 +01:00
d30e2436ab v0.3.0: rename app Daybook -> AgentFeed
Removes the "Daybook" name from everything user- and agent-facing, since no
agents are configured yet and this is the cheap moment to do it.

- UI brand, docs, README, CLAUDE.md -> AgentFeed.
- Ingest env vars DAYBOOK_URL/TOKEN/SOURCE/DATA_DIR/BIND_ADDR/HOST_DATA_DIR/
  KEY_PROVISIONING_API -> AGENTFEED_* (token *values* unchanged).
- Skill: skills/daybook-posting -> skills/agentfeed-posting, daybook_post.py
  -> agentfeed_post.py (+ scripts/ wrapper and its test).
- static/daybook.css -> static/agentfeed.css; localStorage daybook.* ->
  agentfeed.*; templatetag daybook_extras -> agentfeed_extras; multipart
  boundaries; json/csv viewer element ids; daybook_exception_handler ->
  agentfeed_exception_handler; compose service/container -> agentfeed.
- Key-provisioning API now OFF by default (independent of DEBUG); enable with
  AGENTFEED_KEY_PROVISIONING_API=1 on test installs only.
- Posting helper now points at Settings -> API Keys.

Deferred on purpose (a later, separate step): the Django package
`daybook_project`, the `/opt/daybook` install path, and the Forgejo repo name.
The sibling AGENT_DAYBOOK__LLM_TEST name is intentionally untouched.

Full suite: 129 passing; live server verified serving under the new name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 13:10:28 +01:00
4cacd8f64e Issues: file in-app guide (020); archive completed issues
- Add issue 020: in-app user guide (Settings -> Guide), Open, for later.
- Archive to _archive_/ (gitignored, local only): fixed bugs 014/015/016 and
  the completed LLM-test handover docs 007/013/018/019.
- Tracked issues now: 017 (open), 020 (open), README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 11:41:03 +01:00
4fa20e05c2 Docs: add README, document /admin/, refresh for v0.2.x
- Add a top-level README (repo had none): overview, local quick start,
  posting via API keys / the skill endpoint, a docs index, and the admin
  split.
- deployment.md: new "Django Admin (/admin/)" section covering how to log in,
  the is_superuser vs is_staff distinction, how to grant /admin/ access, and
  the raw-edit-bypasses-app-logic caution.
- Refresh constraint wording to per-user multiple API keys.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 11:37:35 +01:00
b382eb857b v0.2.1: top-bar avatar, You/Admin settings groups, admin models
- Replace the gear/username button with a round initials avatar (per-user
  deterministic color via a new daybook_extras template filter); dropdown
  now shows the username and keeps Settings/About/theme/logout. Sets up an
  optional profile photo later.
- Regroup the settings nav into "You" (Account, API Keys, Storage, Demo
  Data) and a superuser-only "Admin" (Users), About separate — the split
  now reflects who each setting affects, not a flat list.
- Register ApiKey (key read-only) and TagColor in Django admin so /admin/
  is complete as the operator break-glass tool, distinct from the themed
  Settings product surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 11:33:23 +01:00
19c1798f90 Release v0.2.0: multiple named API keys per user
Replaces the single per-user DRF token with named, individually-revocable
API keys, so you can run one key per agent and cut off a leaked one without
disrupting the others.

- New ApiKey model (user, name, dbk_-prefixed key, created_at, last_used_at).
  Data migration copies every existing authtoken 1:1 into a "default" named
  key with the same value, so no client (incl. the sibling test project's
  hardcoded test-user key) needs reconfiguring.
- Auth (resolve_token_user) resolves against ApiKey and stamps last_used_at,
  throttled to one write/minute.
- Settings -> API Keys: table with name/key/created/last-used + per-row
  revoke and a create form; keys viewable on demand. Settings -> Account now
  links here; Settings -> Users issues a named initial key.
- Gated provisioning API for test installations: GET/POST /api/keys and
  DELETE /api/keys/{id}, controlled by DAYBOOK_KEY_PROVISIONING_API (unset ->
  follows DEBUG, so off in deployment). Advertised via key_provisioning_api
  in /api/capabilities; 403 when disabled.
- Docs (api/deployment/quickstart/posting-helper), SKILL.md, and env.example
  updated from "token / Settings -> Account" to "key / Settings -> API Keys".
- Tests: new API + UI suites for keys; existing token-based tests moved to
  ApiKey. Full suite 129 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 10:52:43 +01:00
5c46f2aa67 Release v0.1.1: fix collectstatic crash-loop; ship /api/skill
Fixes the deploy blocker found deploying v0.1.0 to cloud-vm-1: the vendored
gridjs.umd.js ended with a //# sourceMappingURL=gridjs.umd.js.map trailer,
but that .map isn't in the repo. The strict CompressedManifestStaticFilesStorage
backend follows the reference and raises MissingFileError, aborting the
entrypoint's collectstatic so the container never served. Stripped the
debug-only trailer (no runtime effect); collectstatic now succeeds from a
pristine checkout (169 copied, 481 post-processed).

Also rolls up this release's already-committed work: GET /api/skill and the
location-agnostic posting-skill rewrite.

- Bump __version__ to 0.1.1.
- Runbook: note that a post-build crash-loop is most likely a collectstatic
  MissingFileError (check docker logs), and how to clear on-server tracked
  drift before checking out a tag.
- CHANGELOG: 0.1.1 entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 16:13:19 +01:00
fb69261218 Add GET /api/skill endpoint serving the posting skill
Serves skills/daybook-posting/SKILL.md so users/agents can fetch the
posting instructions from the running instance instead of hunting for the
file. The server rewrites the DAYBOOK_URL config line to the request's own
origin, so the text handed to an agent already points at the right server;
the recipient only supplies their token.

- Same auth as /api/capabilities (session or bearer token); no user data.
- Raw text/markdown by default, ?format=json returns {version, url, skill}.
- Advertised via skill_url + a /api/skill methods entry in capabilities.
- Ships in the Docker image (COPY . /app, skills/ not dockerignored).
- Tests for auth, origin rewrite, JSON mode, session auth, capabilities ad.
- docs/api.md + CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 15:50:04 +01:00
caeb2bedc7 Rewrite posting skill to be location-agnostic
The skill was authored in this dev workspace, where the Daybook server,
the skill, and the posting agent all share one directory — so it read as
if the agent always runs from a Daybook source checkout on localhost.

- State up front that you post to a running instance that may be anywhere,
  need no source checkout, and are tied to it only via DAYBOOK_URL/TOKEN.
- Drop "from the project root"; reference the bundled helper by absolute
  path (DAYBOOK_POST=.../this-skill/scripts/daybook_post.py).
- Reframe DAYBOOK_URL as set-this-to-your-instance; label 127.0.0.1:8001
  as the local dev fallback only.
- Genericize the hardcoded localhost URL in the image-embed example.
- Add a raw curl fallback for environments without the Python helper.
- Keep frontmatter to name+description (the Skills-API-portable subset).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 15:43:58 +01:00
6859be688e Add CHANGELOG; switch runbook rollback to tags
- CHANGELOG.md started (Keep a Changelog style), with the v0.1.0 release
  notes; versions track the git tags and __version__.
- Runbook §7 now deploys a specific tag (git fetch --tags && checkout
  vX.Y.Z) and rolls back to the previous tag, with a note that rollback
  reverts code not data (restore the data backup if a migration ran).

(The deploy kickoff brief lives in planning/, which is gitignored — it's a
local hand-to-the-agent note, not shipped in the repo.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 14:47:09 +01:00
16737f3ff4 Add version (0.1.0) and a Settings -> About page
Single source of truth: __version__ in daybook_project/__init__.py, starting
at 0.1.0. Surfaced in two places from that constant:
- Settings -> About page (logo, name, version, description), in the settings
  nav and linked from the gear menu.
- GET /api/capabilities now includes "version", so the deploy agent / tester
  can confirm which version is actually running.

Release flow: bump __version__, commit, `git tag vX.Y.Z`, push tag, deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 14:41:02 +01:00
ca8e364010 Fix healthcheck + verify commands; reframe deploy runbook for a capable agent
Real bug: the container healthcheck (and the docs' verify step) hit
/api/capabilities, which returns 403 unauthenticated; curl --fail treats
403 as an error, so a healthy container would report "unhealthy" and the
verify step would look like a failure. Healthcheck now hits /accounts/login/
(200, no auth). Verify commands now print the status code and note not to
use curl -f on the 403.

Also reframed cloud_agent_deploy.md's intro for an Opus-class deploy agent:
it's a guide + Daybook-specific gotchas, not a rigid script — points at the
authoritative sources (repo incl. the git-SSH :2222 note, deploy/ files,
requirements.txt deps auto-installed, entrypoint runs migrate+collectstatic,
docs/deployment.md) and trusts the agent's judgment. Dropped the now-
redundant Source Of Truth section.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 14:32:39 +01:00
a355285f3b Add round-4 (pre-deploy) handover for the LLM test project
Final regression sign-off before deployment. Since #018 the only changes
are cosmetic (table/body fonts), so no new API surface — this round is a
broad regression sweep: full CRUD across all kinds, auth boundaries,
confirmation that every closed issue (#003/#004/#006/#008/#010/#014/#015/
#016) stays fixed, and a sharing re-check. A clean pass is the deploy gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 14:16:53 +01:00
fc9533b402 Use system sans-serif for document body text across all themes
report used Georgia (serif) for body prose, which looked dated/out of place
(it's the default for markdown and html_fragment, so most text artifacts).
Switched it to the system sans stack and pinned that same stack explicitly
in tasks too, so all three document themes (base/report/tasks) share one
font family rather than matching only by inheritance. Headings, table
cells (monospace), and table headers (Arial) are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 14:10:22 +01:00
ebc9156a15 Style table fonts: monospace cells, sans-serif headers
Table cell data (td) now uses a fixed-width stack (Consolas, SF Mono, Menlo,
...) at 0.92em so columns — especially numbers — align and scan cleanly;
headers (th) use Arial/sans for clear contrast against the mono data.
Applies to markdown and html_fragment tables via the shared base.css.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 13:56:13 +01:00
54f8e8ea3b Add round-3 handover doc for the LLM test project
Covers the #014-#016 fixes (sanitization+CSP, nested-JSON 400, ragged-CSV
normalization) and flags the sanitizer as the top round-4 adversarial
target (mutation XSS, attribute/scheme bypasses, CSP presence). Notes #017
is a design issue, not a bug to re-report, and the aesthetic/UI changes
have no API surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 13:47:46 +01:00
31999dcac2 Fix stale theme description in SKILL.md
The document themes (report/tasks/base) now share the unified off-white
background, so they differ in typography/width, not background color.
Updated the --theme description accordingly (was still claiming report has a
distinct "warm background").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 13:46:44 +01:00
69a7e39c4b Auto-adjust artifact sidebar width when the source column drops
When a single source is selected the row's source column is hidden, so the
sidebar now renders 112px (7rem) narrower and the reclaimed space goes to
the preview pane — instead of the sidebar staying the same width and just
widening the title. One stored "base" width drives both modes (wide =
stored, source-selected = stored - 112, floored at 150px); dragging the
splitter in either mode adjusts that base directly, so the width stays
user-settable in whichever mode and the modes track each other by the fixed
offset. Avoids having to re-resize every time you switch modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 13:45:02 +01:00
a41e8dcfa5 Add issue #017: customizable, switchable document theme variants
Design issue capturing the request to let users customize document-artifact
colors and switch between theme variants, instead of everyone getting one
forced theme. Notes the two separable pieces (structure/skin split via CSS
variables as a prerequisite — the themes currently hardcode colors — and a
switching mechanism), the preset-vs-raw-CSS security tradeoff, dark-mode
interaction, and deck as the concrete trigger. References the existing
planning docs on theme variants/palettes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 13:35:32 +01:00
95b159c0df Unify document artifact backgrounds to the off-white content color
markdown/html_fragment (report, tasks, base themes) and the json/csv viewers
each used their own near-white background (cream #fffaf0, light-blue
#f7fbff, or plain white). They now all use #faf9f6 — the same off-white the
preview area uses for image/binary artifacts — so browsing across kinds is
visually consistent.

Left as-is on purpose: deck (intentional dark slide background; switching it
would require reworking its light text colors), html_full (served verbatim,
poster owns styling), and pdf (the browser's own viewer chrome). Existing
json/csv artifacts keep their baked-in #fffaf0, which is visually
indistinguishable from #faf9f6; only new ones get the exact value.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 13:30:36 +01:00
5fd97a29a1 Enlarge the top-bar logo slightly (1.6rem -> 2rem)
Bumps the brand logo a notch; still fits within the existing top-bar
height (the filter toolbar remains the tallest element, so no layout
growth).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 13:20:19 +01:00
7a7499d66e Swap in icon_2 (natively transparent) for the app icon assets
Regenerated logo/favicon/apple-touch from daybook_icon_2.png, a genuinely
transparent PNG (vs the first icon, keyed out of a JPEG checkerboard). The
new source has a subtle neon glow rim: the top-bar logo uses a tight
alpha>=140 crop so it stays crisp on the light chrome, while the square
favicon/apple-touch keep the thin glow (looks good on dark tab bars/tiles).
Placement unchanged — logo sits in the top-left brand corner next to
"AgentFeed".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 13:18:11 +01:00
cf776d3f0b Add app icon: favicon, apple-touch icon, and top-bar logo
Generated a transparent PNG set from the source icon (a JPEG with a baked-in
checkerboard) by keying out the background on saturation — the arrow is
strongly saturated (0.47-0.68) while the grey/white background is 0.00, so
the cutout is clean. Produced static/logo.png (top-bar, natural aspect),
static/favicon.png (32px), and static/apple-touch-icon.png (180px).

Wired favicon + apple-touch links into all three page heads (feed, settings,
login) and placed the logo next to the "AgentFeed" title in the top bar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 13:13:17 +01:00
38f3d0f902 Rework surface colors into a layered greyscale
Establishes a clearer background hierarchy across the app chrome:
- Top bar: its own slightly darker grey (--topbar-bg).
- Both sidebar headers (pane-bars) and the content control bar: grey
  (--pane-bar-bg now matches the sidebar grey instead of white).
- Artifact sidebar: grey background like the source sidebar; the rows
  themselves stay white so they read as cards floating on grey.
- Preview/content area: a light off-white (--content-bg), distinct from
  both the grey chrome and the white rows.

New --topbar-bg and --content-bg vars added to both light and dark
palettes. Also fixes a pre-existing bug from the dark-mode commit where
--scrollbar-thumb and --focus-ring had become self-referential (a
too-broad replace_all had clobbered their own definitions), so scrollbar
and focus-ring colors resolve correctly again. Verified both modes render
coherently.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 12:49:18 +01:00
e257464e7c Center the filter controls in the top bar
The filter form sat at the left edge of its (wide, 1fr) grid cell, jammed
up against the brand. justify-content:center groups it in the middle of the
top bar, above the content area and roughly screen-centered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 12:37:31 +01:00
fbc4b7249b Drop the redundant source column in the artifact list when a source is selected
When a single source is selected, every row in the middle sidebar repeats
that same source, so the source column is pure waste — the pane header
already names the source. The column now only shows under "All sources"
(where it's needed to tell rows apart); selecting a source hides it and
gives the width back to the artifact title. Also drops the now-dangling
row-main border-right in that state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 12:30:49 +01:00
5f872b4b32 Fix #015 (nested-JSON 500) and #016 (ragged CSV data loss)
#015 — deeply nested JSON raised RecursionError (a RuntimeError, not
ValueError) and became an unhandled 500 on two paths:
- json-kind content validation now catches RecursionError -> 400.
- Added a custom DRF EXCEPTION_HANDLER
  (artifacts/exception_handlers.py, wired via REST_FRAMEWORK) mapping
  otherwise-unhandled RecursionError -> 400, covering the parser-level
  crash on deeply nested request bodies (metadata etc.) too.

#016 — wrap_csv_viewer handed Grid.js rows without reconciling width to the
header, so over-long rows silently dropped cells. Now normalizes every row
to the max width: long rows widen the columns (auto-named "Column N"),
short rows pad with "". No cell is dropped.

Both verified live; 4 regression tests added. 106 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 10:57:07 +01:00
5c53643349 Document that html_full is intentionally not validated
Records the decision to keep html_full fully flexible: no sanitization, no
CSP, and no HTML/CSS validation — malformed markup is kept as-is. The
themed kinds (markdown/html_fragment/deck) remain sanitized, which also
normalizes them to well-formed HTML.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 10:50:06 +01:00
e3c5773e8f Fix #014: sanitize themed text kinds + script-blocking CSP (stored XSS)
markdown/deck/html_fragment rendered author-influenced content verbatim,
so <script>/<img onerror>/etc. executed on the Daybook origin — self-XSS
against the owner's session, and (post-sharing) unauthenticated via /s/.

Two layers, both in artifacts/views.py:
- Create-time allowlist sanitization via nh3 (maintained ammonia/Rust
  sanitizer, not deprecated bleach). Keeps formatting/tables/links/images
  (incl. relative /assets URLs) and codehilite markup; drops script, on*
  handlers, style, unknown tags, and javascript:/data: URLs. html_full is
  deliberately NOT sanitized (documented verbatim escape hatch).
- Serve-time CSP (script-src 'none') for the sanitized kinds, on both
  /a/{id} and the unauthenticated /s/{token} route. Being a response
  header, it also neutralizes scripts in artifacts stored before this fix.
  Not applied to html_full or json/csv (json/csv data is already escaped;
  their viewers need their own JS).

Verified live across all kinds; 7 regression tests added. 102 tests pass.

Not addressed (noted in the issue): CSV formula injection,
CSRF_COOKIE_HTTPONLY (moot with XSS closed), nonce CSP for json/csv/full.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 10:44:49 +01:00
6d2fe2147c Archive resolved issues; add open issues #014-#016
Moves the 11 Fixed issues (#001-006, #008-012) into issues/_archive_/,
which is gitignored — they stay on local disk and in git history but drop
out of the committed tree, keeping the issues/ dir focused on what's open.
The sibling test project reads this dir by local path, so it still sees
the archived files. Adds the three new open issues found in round-3
testing: #014 (stored XSS in markdown/deck/html_fragment — High), #015
(RecursionError -> 500 on deeply nested JSON), #016 (ragged CSV rows lose
data in the rendered table). Documents the archive convention in the README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 10:27:45 +01:00
292c386209 Add round-2 handover doc for the LLM test project
Covers everything shipped since the #007 handoff: the json/csv kinds,
.xlsx conversion, public sharing, syntax highlighting/table styling, and
the #008-#012 fixes. Prioritizes round-3 probe suggestions by risk, with
sharing (the new unauthenticated surface) and the #010 quota race (no
automated coverage) called out as the highest-value targets.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 20:53:12 +01:00
9a3b381f39 Add light/dark mode toggle for the UI
The UI stylesheet already centralized its palette in a :root block, so this
mostly meant (1) migrating the ~30 colors still hardcoded in rules into
variables so they flip in one place, and (2) adding a [data-theme="dark"]
override palette. Artifact rendering is untouched — those use the separate
static/themes/*.css and the preview backdrop stays white by design, since
artifacts (charts, reports) are authored light.

- Toggle lives in the gear-menu dropdown (Dark mode / Light mode), persists
  to localStorage.
- A tiny inline script in each page <head> (feed, settings, login) applies
  the stored theme before first paint, so dark-mode users never see a white
  flash on load.
- color-scheme: dark on the dark palette makes native form controls
  (inputs, buttons) render dark too.
- Semantic accents (kept=green, shared=blue, today, danger) got
  dark-appropriate variants; status/data-viz colors (usage bar, tag chips)
  and the white artifact backdrop are intentionally left as-is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 17:14:56 +01:00
c9edac62cf Review docs/api.md and docs/llm_posting_helper.md for accuracy and completeness
Same pass as the SKILL.md review. Fixed factually-wrong content and filled
gaps against the current code.

docs/api.md:
- Capabilities example response was stale: artifact_kinds missing json/csv,
  raw_supported_for missing json/csv. Corrected to match live output, with
  a note that the endpoint is the authoritative source.
- "Office document conversion is not implemented" is now false - replaced
  with the accurate scope (.xlsx -> csv only; .docx/.pptx/.xls not).
- Source Fields overview table omitted source_prompt entirely; added it,
  plus a note that source_name/source_id/source_prompt must be strings
  (400, not coerced) and metadata must be an object.
- Kinds table: noted tables now render styled in markdown/html_fragment,
  and made explicit that html_full is served verbatim with no styling.
- MVP constraints: noted the public-share-link exception to "feeds private
  per user" and clarified PATCH edits metadata only.

docs/llm_posting_helper.md:
- Added .xlsx upload, --upload-image image embedding, a Source & Theme
  Flags table (all six source-*/theme flags were undocumented here), and a
  note that --meta/--meta-json merge and are distinct from tags.
- Added a scope note that the helper only creates - update/delete need the
  API, sharing is feed-UI-only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 16:49:19 +01:00
d2d970f33d Review and correct SKILL.md for accuracy and completeness
Found six real flags with zero mention anywhere in the doc:
--source-type, --source-name, --source-id, --source-prompt, --meta-json,
--dry-run. Added a new "Source & Provenance Fields" section for the
source-* flags and covered the rest inline/in Rules.

Fixed:
- html_full's cheat-sheet entry overstated the requirement as needing
  doctype/html/head/body; the actual check only requires <html>.
- The "Images Inside Content" section had grown to cover four unrelated
  topics (images, CSV/JSON, xlsx, code-as-markdown) under a header
  describing only the first one. Split into properly-titled sections:
  Images Inside Content, CSV/JSON/Spreadsheets, Other Code.
- An unclosed parenthesis in the xlsx limitations paragraph, rewritten as
  a bulleted list for clarity while fixing it.
- Nothing anywhere mentioned that this script only creates artifacts -
  update/delete need the raw API, sharing is feed-UI-only with no API
  path at all. Added an explicit scope note.
- --source-name's actual default (the resolved source, which can come
  from DAYBOOK_SOURCE, not just --source) was previously undocumented
  entirely, so "same as --source" would have been imprecise once added.

Added: available --theme values (report/tasks/base) and which kinds
ignore it, and a note that markdown/html_fragment tables now render
styled automatically (from this session's earlier CSS pass).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 16:42:15 +01:00
4b5ec09ae2 Convert uploaded .xlsx files to csv artifacts server-side
Uploading a .xlsx file no longer becomes a generic downloadable binary -
it's read server-side with openpyxl and converted to a csv artifact,
rendering in the same interactive grid as a native CSV post. Detected by
content type or .xlsx extension (some clients send generic/incorrect
content types for multipart uploads). Only the active sheet converts;
formula cells use whatever cached result the spreadsheet software last
saved (openpyxl doesn't evaluate formulas itself). Corrupt/unreadable
files return 400.

Since the existing posting script already forwards any non-text file via
multipart with no special-casing, `daybook_post.py balance_sheet.xlsx`
already works with zero script changes - verified live.

Two real bugs found and fixed while building this, both now covered by
regression tests:
- {**a_querydict, ...} silently wrapped every value (including title) in
  a list - Python's ** spread on a dict subclass bypasses QueryDict's
  __getitem__ override and exposes its internal one-list-per-key storage
  directly. Fixed by building the delegated dict explicitly via .get().
- Pre-setting None for absent optional fields (e.g. source_prompt) defeated
  create_text_artifact's own data.get(field, default) calls, since .get()
  only falls through to its default when the key is *missing*, not when
  the value is None - caused a NOT NULL constraint failure. Fixed by only
  setting keys that were actually provided.
- csv.writer's default "\r\n" terminator was getting doubled by
  Path.write_text()'s own newline translation on Windows, producing a
  blank line between every row. Fixed with lineterminator="\n".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 16:29:39 +01:00
4145c37dfe Note that html_full can reference Daybook's own vendored JS libraries
Grid.js and renderjson (vendored for the csv/json kinds) are reachable as
plain static files from any artifact, including html_full - a better
default than an external CDN for table/JSON rendering specifically, since
it works offline and has no third-party dependency.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 16:04:53 +01:00
91ea19d26b Add per-artifact public sharing
Any artifact can now be made publicly viewable via a share link, no login
required - opt-in per artifact, toggled from the feed UI next to Keep.

- Artifact.share_token: nullable, unique, high-entropy (secrets.token_urlsafe(32)).
  NULL = not shared; SQL treats each NULL as distinct so uniqueness still
  holds for the many not-shared rows.
- New public route GET /s/<token> (shared_artifact_view), completely
  unauthenticated, serving the same rendered/raw content as the owner
  route via a shared serve_artifact_file() helper.
- toggle_artifact_share: generates/clears the token: mirrors
  toggle_artifact_keep's pattern.
- Fixed a real gap this surfaced: embedded images in a shared artifact's
  content would otherwise break for an anonymous visitor, since asset_view
  was owner-only. It now also allows access when the asset's parent
  artifact is currently shared - unattached (orphaned) assets stay private
  regardless.
- serialize_artifact now reports is_shared/share_url so the API reflects
  sharing status (read-only for now; toggling stays UI-only).
- Feed UI: Share button (mirrors Keep's toggle interaction), a copyable
  share-link box that appears when shared, and a "shared" badge on the
  artifact row - distinct blue accent from Keep's green so both can be
  visible on the same row without confusion.

Verified live end-to-end (toggle on/off, public access, fake-token 404,
old-link-dies-on-unshare, embedded-asset access before/after sharing)
before writing the 12 automated tests in tests/views/test_sharing.py.
Also fixed two unrelated stale spots noticed in docs/api.md while adding
the Sharing section: the example response was missing is_kept/size_bytes,
and the raw-content table didn't list json/csv (added earlier this
session but never documented there).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 16:00:39 +01:00
e137725cab Add json/csv support to the posting script (issue #012)
TEXT_KINDS and TEXT_SUFFIXES now include json/csv, with matching
sniff_text_kind branches so both explicit --kind and positional auto-mode
work. Added validate_payload's JSON shape check mirroring the server.
Updated SKILL.md/docs/llm_posting_helper.md with real usage examples,
replacing the stale --as-markdown workaround guidance (kept only for
genuinely unstructured code, which still has no dedicated kind). Also
fixed an unrelated stale doc line found in the same table: html_full's
validation was documented as requiring <!doctype AND <html, but only
<html is actually required.

Verified via dry-run (explicit --kind, auto-mode, invalid-JSON rejection)
and one real end-to-end post through the script against the live dev
instance.

Added tests/scripts/test_daybook_post.py - 17 tests, plain unittest, zero
new dependencies, loaded via importlib since the script isn't a package.
Closes the test-coverage question the issue raised.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 15:38:47 +01:00