Compare commits

..

No commits in common. "v0.6.3" and "v0.6.2" have entirely different histories.

7 changed files with 32 additions and 52 deletions

View file

@ -9,20 +9,6 @@ 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
@ -235,8 +221,7 @@ 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.3...HEAD [Unreleased]: https://git.cygnus.systems/peter/daybook/compare/v0.6.2...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.3" __version__ = "0.6.2"

View file

@ -1139,25 +1139,20 @@ kbd {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-shrink: 0; flex-shrink: 0;
gap: 0.25rem; /* space Keep / Deleted apart */ padding: 0.4rem 0.6rem 0.2rem;
padding: 0.4rem 0.6rem 0.3rem;
} }
/* "Special" sidebar entries — "All sources" and the Keep/Deleted folders — /* "All sources" — the primary entry, visually distinct from individual sources:
share a distinct look (bold + subtle blue tint) to set them apart from the bold, with a subtle blue tint (works over both light and dark surfaces). */
individual sources. */ .all-sources-link {
.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 }} message{{ artifacts|length|pluralize }}</span> <span>{{ artifacts|length }} artifact{{ 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 messages</h3> <h3>No artifacts</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 message loaded{% endif %} {% if selected_artifact %}{{ selected_artifact.title }}{% else %}No artifact 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 Message</h2> <h2>Binary Artifact</h2>
<p>This message type cannot be previewed inline yet.</p> <p>This artifact 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 message loaded</h2> <h2>No artifact loaded</h2>
<p>Press <kbd>Right Arrow</kbd> or select a message from the middle column to preview it.</p> <p>Press <kbd>Right Arrow</kbd> or select an artifact from the middle column to preview it.</p>
{% else %} {% else %}
<h2>No messages for this day</h2> <h2>No artifacts 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 message link", absoluteUrl); window.prompt("Copy artifact 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 message loaded"; previewTitle.textContent = "No artifact 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} message${total === 1 ? "" : "s"}`; header.textContent = `${total} artifact${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 Message</h2><p>This message 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 Artifact</h2><p>This artifact 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 message loaded</h2><p>Press <kbd>Right Arrow</kbd> or select a message from the middle column to preview it.</p></div>`; 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>`;
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} message${total === 1 ? "" : "s"}`; header.textContent = `${total} artifact${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 messages — reports, HTML, decks, images, PDFs, JSON, CSV — and each publish artifacts — 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 key from <p class="storage-note">Post to it with a personal API token from
Settings&nbsp;→&nbsp;API&nbsp;Keys; the current API capabilities (including Settings&nbsp;→&nbsp;Account; the current API capabilities (including this
this version) are available at <code>/api/capabilities</code>.</p> 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 message of every supported kind (markdown, HTML fragment, full HTML, deck, image, PDF, binary), tagged "demo" and dated today.</p> <p>Generate one sample artifact 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 }} message{{ artifact_count|pluralize }} total</li> <li>{{ artifact_count }} artifact{{ 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> messages are deleted When your quota fills, the oldest <strong>unkept</strong> artifacts are deleted
automatically to make room for new ones. Click <strong>Keep</strong> on a message automatically to make room for new ones. Click <strong>Keep</strong> on an artifact
to protect it. If the quota is full of kept messages, new posts are rejected until to protect it. If the quota is full of kept artifacts, 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 storage disk: <strong>{{ disk_free|filesizeformat }}</strong></li> <li>Free space on the artifact 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">