Compare commits

..

1 commit

Author SHA1 Message Date
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
7 changed files with 52 additions and 32 deletions

View file

@ -9,6 +9,20 @@ former name, "Daybook".)
_Nothing yet._ _Nothing yet._
## [0.6.3] — 2026-09-07
### Changed
- **User-facing wording: "artifact(s)" → "messages"** throughout the UI (feed
count, empty states, preview, About, Storage). The model, API
(`/api/artifacts`), and posting skill keep "artifact" as the internal term —
no contract change for agents.
- Sidebar: the Keep/Deleted folders now have spacing between them and share the
"All sources" bold+blue-tint styling (they're special folders).
### Fixed
- About page now points to Settings → API Keys (was the stale "Settings →
Account / personal API token").
## [0.6.2] — 2026-09-07 ## [0.6.2] — 2026-09-07
### Changed ### Changed
@ -221,7 +235,8 @@ First tagged release.
- Dockerised (Gunicorn + whitenoise), SQLite, `git`-based deploy with a - Dockerised (Gunicorn + whitenoise), SQLite, `git`-based deploy with a
runbook (`deploy/cloud_agent_deploy.md`) and health check. runbook (`deploy/cloud_agent_deploy.md`) and health check.
[Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.6.2...HEAD [Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.6.3...HEAD
[0.6.3]: https://git.cygnus.systems/peter/daybook/compare/v0.6.2...v0.6.3
[0.6.2]: https://git.cygnus.systems/peter/daybook/compare/v0.6.1...v0.6.2 [0.6.2]: https://git.cygnus.systems/peter/daybook/compare/v0.6.1...v0.6.2
[0.6.1]: https://git.cygnus.systems/peter/daybook/compare/v0.6.0...v0.6.1 [0.6.1]: https://git.cygnus.systems/peter/daybook/compare/v0.6.0...v0.6.1
[0.6.0]: https://git.cygnus.systems/peter/daybook/compare/v0.5.1...v0.6.0 [0.6.0]: https://git.cygnus.systems/peter/daybook/compare/v0.5.1...v0.6.0

View file

@ -1,4 +1,4 @@
# Single source of truth for the app version. Bump this, commit, then tag the # Single source of truth for the app version. Bump this, commit, then tag the
# commit `vX.Y.Z` and deploy. Surfaced on Settings -> About and in # commit `vX.Y.Z` and deploy. Surfaced on Settings -> About and in
# GET /api/capabilities ("version"). # GET /api/capabilities ("version").
__version__ = "0.6.2" __version__ = "0.6.3"

View file

@ -1139,20 +1139,25 @@ kbd {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-shrink: 0; flex-shrink: 0;
padding: 0.4rem 0.6rem 0.2rem; gap: 0.25rem; /* space Keep / Deleted apart */
padding: 0.4rem 0.6rem 0.3rem;
} }
/* "All sources" — the primary entry, visually distinct from individual sources: /* "Special" sidebar entries — "All sources" and the Keep/Deleted folders —
bold, with a subtle blue tint (works over both light and dark surfaces). */ share a distinct look (bold + subtle blue tint) to set them apart from the
.all-sources-link { individual sources. */
.all-sources-link,
.folder-link {
font-weight: 600; font-weight: 600;
} }
.all-sources-link:not(.selected) { .all-sources-link:not(.selected),
.folder-link:not(.selected) {
background: rgba(74, 110, 168, 0.14); background: rgba(74, 110, 168, 0.14);
} }
.all-sources-link:not(.selected):hover { .all-sources-link:not(.selected):hover,
.folder-link:not(.selected):hover {
background: rgba(74, 110, 168, 0.22); background: rgba(74, 110, 168, 0.22);
} }

View file

@ -73,7 +73,7 @@
{% if selected_folder == 'deleted' %} {% if selected_folder == 'deleted' %}
<button type="button" class="empty-trash-button" data-empty-trash>Empty Trash</button> <button type="button" class="empty-trash-button" data-empty-trash>Empty Trash</button>
{% else %} {% else %}
<span>{{ artifacts|length }} artifact{{ artifacts|length|pluralize }}</span> <span>{{ artifacts|length }} message{{ artifacts|length|pluralize }}</span>
{% endif %} {% endif %}
</div> </div>
@ -82,7 +82,7 @@
{% include "artifacts/_day_groups.html" with has_more=older_count next_before=window_start|date:"Y-m-d" %} {% include "artifacts/_day_groups.html" with has_more=older_count next_before=window_start|date:"Y-m-d" %}
{% else %} {% else %}
<div class="empty compact"> <div class="empty compact">
<h3>No artifacts</h3> <h3>No messages</h3>
<p>Nothing in the last {{ 7 }} days for these filters.</p> <p>Nothing in the last {{ 7 }} days for these filters.</p>
</div> </div>
{% endif %} {% endif %}
@ -94,7 +94,7 @@
<section class="preview-pane"> <section class="preview-pane">
<header class="pane-bar preview-actions" data-actions> <header class="pane-bar preview-actions" data-actions>
<div class="preview-title" data-preview-title {% if selected_artifact %}title="{{ selected_artifact.title }}"{% endif %}> <div class="preview-title" data-preview-title {% if selected_artifact %}title="{{ selected_artifact.title }}"{% endif %}>
{% if selected_artifact %}{{ selected_artifact.title }}{% else %}No artifact loaded{% endif %} {% if selected_artifact %}{{ selected_artifact.title }}{% else %}No message loaded{% endif %}
</div> </div>
<nav class="view-toggle" aria-label="Preview mode"> <nav class="view-toggle" aria-label="Preview mode">
<a class="{% if selected_view == 'rendered' %}selected{% endif %}{% if not selected_artifact %} disabled{% endif %}" href="{% if selected_artifact %}{{ rendered_url }}{% else %}#{% endif %}" data-view="rendered" {% if not selected_artifact %}aria-disabled="true"{% endif %}>Rendered</a> <a class="{% if selected_view == 'rendered' %}selected{% endif %}{% if not selected_artifact %} disabled{% endif %}" href="{% if selected_artifact %}{{ rendered_url }}{% else %}#{% endif %}" data-view="rendered" {% if not selected_artifact %}aria-disabled="true"{% endif %}>Rendered</a>
@ -124,8 +124,8 @@
<img class="preview-image" src="{% url 'artifact' selected_artifact.id %}" alt="{{ selected_artifact.title }}"> <img class="preview-image" src="{% url 'artifact' selected_artifact.id %}" alt="{{ selected_artifact.title }}">
{% elif selected_artifact.kind == "binary" %} {% elif selected_artifact.kind == "binary" %}
<div class="empty preview-empty"> <div class="empty preview-empty">
<h2>Binary Artifact</h2> <h2>Binary Message</h2>
<p>This artifact type cannot be previewed inline yet.</p> <p>This message type cannot be previewed inline yet.</p>
<p><a href="{% url 'artifact' selected_artifact.id %}">Open or download it</a>.</p> <p><a href="{% url 'artifact' selected_artifact.id %}">Open or download it</a>.</p>
</div> </div>
{% else %} {% else %}
@ -135,10 +135,10 @@
{% else %} {% else %}
<div class="empty preview-empty"> <div class="empty preview-empty">
{% if artifacts %} {% if artifacts %}
<h2>No artifact loaded</h2> <h2>No message loaded</h2>
<p>Press <kbd>Right Arrow</kbd> or select an artifact from the middle column to preview it.</p> <p>Press <kbd>Right Arrow</kbd> or select a message from the middle column to preview it.</p>
{% else %} {% else %}
<h2>No artifacts for this day</h2> <h2>No messages for this day</h2>
<p>This day is ready for new agent output.</p> <p>This day is ready for new agent output.</p>
{% endif %} {% endif %}
</div> </div>
@ -239,7 +239,7 @@
try { try {
await navigator.clipboard.writeText(absoluteUrl); await navigator.clipboard.writeText(absoluteUrl);
} catch { } catch {
window.prompt("Copy artifact link", absoluteUrl); window.prompt("Copy message link", absoluteUrl);
} }
const original = button.textContent; const original = button.textContent;
button.textContent = "Copied"; button.textContent = "Copied";
@ -348,7 +348,7 @@
if (!actions) return; if (!actions) return;
const previewTitle = actions.querySelector("[data-preview-title]"); const previewTitle = actions.querySelector("[data-preview-title]");
if (previewTitle) { if (previewTitle) {
previewTitle.textContent = "No artifact loaded"; previewTitle.textContent = "No message loaded";
previewTitle.removeAttribute("title"); previewTitle.removeAttribute("title");
} }
actions.querySelectorAll("[data-view]").forEach((link) => { actions.querySelectorAll("[data-view]").forEach((link) => {
@ -484,7 +484,7 @@
const header = document.querySelector(".artifact-sidebar .pane-heading span"); const header = document.querySelector(".artifact-sidebar .pane-heading span");
if (!header) return; if (!header) return;
const total = Math.max(0, (parseInt(header.textContent, 10) || 0) + delta); const total = Math.max(0, (parseInt(header.textContent, 10) || 0) + delta);
header.textContent = `${total} artifact${total === 1 ? "" : "s"}`; header.textContent = `${total} message${total === 1 ? "" : "s"}`;
}; };
const bumpFolderCount = (folder, delta) => { const bumpFolderCount = (folder, delta) => {
@ -632,7 +632,7 @@
: kind === "image" : kind === "image"
? `<img class="preview-image" src="${source}" alt="${title}">` ? `<img class="preview-image" src="${source}" alt="${title}">`
: kind === "binary" : kind === "binary"
? `<div class="empty preview-empty"><h2>Binary Artifact</h2><p>This artifact type cannot be previewed inline yet.</p><p><a href="${source}">Open or download it</a>.</p></div>` ? `<div class="empty preview-empty"><h2>Binary Message</h2><p>This message type cannot be previewed inline yet.</p><p><a href="${source}">Open or download it</a>.</p></div>`
: `<iframe class="preview-frame" src="${source}" title="${title}"></iframe>`; : `<iframe class="preview-frame" src="${source}" title="${title}"></iframe>`;
previewBody.innerHTML = `<div class="preview-frame-wrap">${content}</div>`; previewBody.innerHTML = `<div class="preview-frame-wrap">${content}</div>`;
@ -647,7 +647,7 @@
} }
allRows().forEach((candidate) => candidate.classList.remove("selected")); allRows().forEach((candidate) => candidate.classList.remove("selected"));
disableActions(); disableActions();
previewBody.innerHTML = `<div class="empty preview-empty"><h2>No artifact loaded</h2><p>Press <kbd>Right Arrow</kbd> or select an artifact from the middle column to preview it.</p></div>`; previewBody.innerHTML = `<div class="empty preview-empty"><h2>No message loaded</h2><p>Press <kbd>Right Arrow</kbd> or select a message from the middle column to preview it.</p></div>`;
window.history.pushState({}, "", noPreviewUrl()); window.history.pushState({}, "", noPreviewUrl());
const currentSource = document.querySelector(".source-link.selected"); const currentSource = document.querySelector(".source-link.selected");
if (currentSource) currentSource.focus({preventScroll: true}); if (currentSource) currentSource.focus({preventScroll: true});
@ -885,7 +885,7 @@
const header = document.querySelector(".artifact-sidebar .pane-heading span"); const header = document.querySelector(".artifact-sidebar .pane-heading span");
if (header) { if (header) {
const total = (parseInt(header.textContent, 10) || 0) + data.count; const total = (parseInt(header.textContent, 10) || 0) + data.count;
header.textContent = `${total} artifact${total === 1 ? "" : "s"}`; header.textContent = `${total} message${total === 1 ? "" : "s"}`;
} }
} catch { } catch {
// ignore; retry next tick // ignore; retry next tick

View file

@ -10,11 +10,11 @@
</div> </div>
<p>A self-hosted, multi-user journal where AI agents, scripts, or people <p>A self-hosted, multi-user journal where AI agents, scripts, or people
publish artifacts — reports, HTML, decks, images, PDFs, JSON, CSV — and each publish messages — reports, HTML, decks, images, PDFs, JSON, CSV — and each
logged-in user browses their own time-ordered, label-filterable feed. The logged-in user browses their own time-ordered, label-filterable feed. The
server owns IDs, styling, and storage; agents publish content, not files.</p> server owns IDs, styling, and storage; agents publish content, not files.</p>
<p class="storage-note">Post to it with a personal API token from <p class="storage-note">Post to it with a personal API key from
Settings&nbsp;→&nbsp;Account; the current API capabilities (including this Settings&nbsp;→&nbsp;API&nbsp;Keys; the current API capabilities (including
version) are available at <code>/api/capabilities</code>.</p> this version) are available at <code>/api/capabilities</code>.</p>
{% endblock %} {% endblock %}

View file

@ -2,7 +2,7 @@
{% block settings_content %} {% block settings_content %}
<h2>Demo Data</h2> <h2>Demo Data</h2>
<p>Generate one sample artifact of every supported kind (markdown, HTML fragment, full HTML, deck, image, PDF, binary), tagged "demo" and dated today.</p> <p>Generate one sample message of every supported kind (markdown, HTML fragment, full HTML, deck, image, PDF, binary), tagged "demo" and dated today.</p>
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<button type="submit">Generate Demo Data</button> <button type="submit">Generate Demo Data</button>

View file

@ -11,14 +11,14 @@
</p> </p>
<ul class="storage-facts"> <ul class="storage-facts">
<li>{{ artifact_count }} artifact{{ artifact_count|pluralize }} total</li> <li>{{ artifact_count }} message{{ artifact_count|pluralize }} total</li>
<li>{{ kept_count }} kept ({{ kept_bytes|filesizeformat }}) &mdash; never auto-deleted</li> <li>{{ kept_count }} kept ({{ kept_bytes|filesizeformat }}) &mdash; never auto-deleted</li>
</ul> </ul>
<p class="storage-note"> <p class="storage-note">
When your quota fills, the oldest <strong>unkept</strong> artifacts are deleted When your quota fills, the oldest <strong>unkept</strong> messages are deleted
automatically to make room for new ones. Click <strong>Keep</strong> on an artifact automatically to make room for new ones. Click <strong>Keep</strong> on a message
to protect it. If the quota is full of kept artifacts, new posts are rejected until to protect it. If the quota is full of kept messages, new posts are rejected until
you free space. you free space.
</p> </p>
@ -26,7 +26,7 @@
<h3>Admin</h3> <h3>Admin</h3>
<ul class="storage-facts"> <ul class="storage-facts">
<li>Total quota allocated across all users: <strong>{{ total_allocated|filesizeformat }}</strong></li> <li>Total quota allocated across all users: <strong>{{ total_allocated|filesizeformat }}</strong></li>
<li>Free space on the artifact disk: <strong>{{ disk_free|filesizeformat }}</strong></li> <li>Free space on the storage disk: <strong>{{ disk_free|filesizeformat }}</strong></li>
</ul> </ul>
{% if total_allocated > disk_free %} {% if total_allocated > disk_free %}
<p class="storage-warning"> <p class="storage-warning">