From 3f4fccdba2823a16b782354a09e27568a5661c19 Mon Sep 17 00:00:00 2001 From: Andrea Date: Sat, 26 Sep 2026 17:27:21 +0200 Subject: [PATCH] Enhance documentation, update release process, and improve UI elements --- .github/copilot-instructions.md | 1 + .github/skills/faerro-kb-release/SKILL.md | 4 +- README.md | 5 +-- frontend/app.js | 3 +- frontend/favicon.svg | 4 ++ frontend/index.html | 6 +-- frontend/manifest.webmanifest | 9 +++- frontend/styles.css | 12 ++--- frontend/sw.js | 4 +- scripts/publish-image.sh | 55 +++++++++++++++++++++-- 10 files changed, 80 insertions(+), 23 deletions(-) create mode 100644 frontend/favicon.svg diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c8f45e0..f30fc21 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -21,6 +21,7 @@ Faerro KB is a self-hosted voice-note inbox designed for an iPhone-installed web - Keep changes scoped. Inspect the owning code path, add or update focused tests for behavior changes, and run the narrowest relevant checks available. - Update README/UI documentation when behavior, deployment, security, backup, or platform limitations change. Do not claim a capability that iOS or browsers cannot guarantee. - Preserve user data and existing local changes. Do not perform destructive migrations or remove stored recordings without explicit confirmation and a recovery path. +- At the end of each chat, check whether it introduced durable project requirements, decisions, workflows, or corrections missing from these instructions or a relevant skill. Update the narrowest relevant guidance file when the change is clearly reusable and within the user's authorized scope; otherwise note the gap and ask before expanding scope. Do not record transient task details or sensitive information. ## Releases diff --git a/.github/skills/faerro-kb-release/SKILL.md b/.github/skills/faerro-kb-release/SKILL.md index 3f2899b..229addb 100644 --- a/.github/skills/faerro-kb-release/SKILL.md +++ b/.github/skills/faerro-kb-release/SKILL.md @@ -10,8 +10,8 @@ Use this skill when asked to tag or publish a Faerro KB image. The release proce ## Procedure 1. Check the worktree and identify the exact commit being released. Do not tag unrelated or unreviewed changes. -2. Use the release version explicitly supplied by the user. If no version was supplied and no approved version is established in the task, ask instead of guessing. Tags must be exact `MAJOR.MINOR.PATCH`, optionally prefixed with `v`. -3. Check that the chosen tag is unused and attach it to the intended commit. +2. The script selects the next patch version by default. Use `--minor` or `--major` only when the user requests that increment; lower components are reset. Tags must be exact `MAJOR.MINOR.PATCH`. +3. The script creates an unused local tag on the checked-out commit; do not push Git tags to a remote unless requested. 4. Confirm Docker is available. For non-interactive authentication, put `GITEA_USERNAME` and a package-write `GITEA_TOKEN` in the ignored `.env` file. The script parses only these keys and sends the token through Docker's `--password-stdin`; never put credentials in tracked files, command-line arguments, or chat. Without these settings it relies on Docker's existing login credentials. 5. Run `./scripts/publish-image.sh` from the repository. It builds, logs in when `.env` credentials are set, and pushes both the versioned tag and `latest`. 6. Report the tagged commit, image tags, and actual build/push result. A successful local build is not a successful release if either registry push fails. diff --git a/README.md b/README.md index 9f7180f..50ec384 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,15 @@ docker compose up --build ## Releases -Git tags are the source of truth for release versions. From the repository root, create a semantic version tag on the commit to release, then run the publish script: +Git tags are the source of truth for release versions. From the repository root, run the publish script to create the next patch tag and publish it: ```sh cp .env.example .env # Set GITEA_USERNAME and GITEA_TOKEN in .env, using a Gitea package-write token. -git tag 0.0.2 ./scripts/publish-image.sh ``` -The script requires Docker and Git, and publishes both the versioned image (for example, `gitea.faerro.it/andrea/faerro-kb:0.0.2`) and `latest`. Tags may be plain versions or prefixed with `v`. `.env` is excluded from Git; the script reads the Gitea credentials from it and passes the token to Docker through standard input. If those credentials are unset, the script uses Docker's existing login credentials. +Pass `--minor` to increment the minor version and reset patch to zero, or `--major` to increment the major version and reset minor and patch to zero. The options cannot be combined. The script requires at least one existing `MAJOR.MINOR.PATCH` Git tag, creates the next local tag on the checked-out commit, and publishes both the versioned image and `latest`. `.env` is excluded from Git; the script reads the Gitea credentials from it and passes the token to Docker through standard input. If those credentials are unset, the script uses Docker's existing login credentials. Open `http://localhost` for a desktop smoke test. Set `HTTP_PORT` to publish the app on a different host port (for example, `HTTP_PORT=8000 docker compose up`). For iPhone use, serve the app over HTTPS through your VPN/reverse proxy. iOS only grants microphone access in a secure context. Keep the service reachable only through your VPN and firewall; this first version has no login layer. diff --git a/frontend/app.js b/frontend/app.js index 79a1935..ae59053 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -4,7 +4,7 @@ const MAX_RECORDING_MS = 30 * 60 * 1000; const elements = Object.fromEntries([ 'record-button', 'record-title', 'record-help', 'record-time', 'record-meter-fill', 'capture-message', 'queue-list', 'queue-empty', 'queue-count', 'sync-button', 'archive-list', 'archive-empty', - 'network-state', 'date-label', 'storage-status', + 'network-state', 'storage-status', ].map((id) => [id, document.getElementById(id)])); let databasePromise; @@ -304,7 +304,6 @@ window.addEventListener('offline', updateNetwork); window.addEventListener('focus', () => { updateNetwork(); syncQueue(); loadAwaitingNotes(); }); document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') syncQueue(); }); -elements['date-label'].textContent = new Intl.DateTimeFormat(undefined, { weekday: 'short', month: 'short', day: 'numeric' }).format(new Date()).toUpperCase(); updateNetwork(); renderQueue().then(syncQueue).catch(() => setMessage('Browser storage is unavailable. Check Safari storage settings.', true)); loadAwaitingNotes(); diff --git a/frontend/favicon.svg b/frontend/favicon.svg new file mode 100644 index 0000000..6b3bd07 --- /dev/null +++ b/frontend/favicon.svg @@ -0,0 +1,4 @@ + + + V + \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index bc573e6..04c9860 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7,24 +7,24 @@ Faerro KB +
- Vvoice kb + VFaerro KB Checking
-
PERSONAL ARCHIVE
- Ready when you are + Ready Tap to record a voice note
diff --git a/frontend/manifest.webmanifest b/frontend/manifest.webmanifest index d8433e7..dbd0302 100644 --- a/frontend/manifest.webmanifest +++ b/frontend/manifest.webmanifest @@ -5,5 +5,12 @@ "display": "standalone", "background_color": "#f5f3ed", "theme_color": "#f5f3ed", - "icons": [] + "icons": [ + { + "src": "/favicon.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any maskable" + } + ] } \ No newline at end of file diff --git a/frontend/styles.css b/frontend/styles.css index 3ec5962..a0ca459 100644 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -28,7 +28,7 @@ main { max-width: 1040px; margin: auto; padding: 0 24px; } h1 { margin: 24px 0 11px; font: 500 clamp(42px, 7vw, 72px)/0.99 var(--serif); letter-spacing: 0; } h1 em { color: var(--moss); font-weight: 500; } .intro { color: #606b63; margin: 0 0 28px; font-size: 14px; } -.recorder-row { max-width: 590px; min-height: 78px; padding: 14px 18px 14px 14px; display: flex; align-items: center; gap: 16px; background: #fffefa; border: 1px solid var(--line); } +.recorder-row { max-width: 590px; min-height: 78px; margin-inline: auto; padding: 14px 18px 14px 14px; display: flex; align-items: center; gap: 16px; background: #fffefa; border: 1px solid var(--line); } .record-button { width: 50px; height: 50px; border: 0; border-radius: 50%; background: var(--coral); display: grid; place-items: center; cursor: pointer; flex: 0 0 auto; transition: background .2s ease, transform .2s ease; } .record-button:hover { transform: scale(1.04); } .record-button.recording { background: var(--ink); } @@ -38,15 +38,15 @@ h1 em { color: var(--moss); font-weight: 500; } .record-copy strong { font-size: 14px; font-weight: 600; } .record-copy span { color: var(--muted); font-size: 12px; } #record-time { color: var(--muted); font: 12px var(--mono); font-variant-numeric: tabular-nums; } -.record-meter { height: 2px; max-width: 590px; background: #e4e3dc; } +.record-meter { height: 2px; max-width: 590px; margin-inline: auto; background: #e4e3dc; } .record-meter span { display: block; height: 100%; width: 0; background: var(--coral); transition: width .25s linear; } -.capture-message { height: 20px; max-width: 590px; padding-top: 6px; color: var(--muted); font-size: 11px; } +.capture-message { height: 20px; max-width: 590px; margin-inline: auto; padding-top: 6px; color: var(--muted); font-size: 11px; text-align: center; } .queue-section, .archive-section { padding: 30px 0 34px; border-bottom: 1px solid var(--line); } -.section-heading { display: flex; align-items: center; justify-content: space-between; gap: 18px; } -.section-heading .section-kicker { justify-content: flex-start; } +.section-heading { position: relative; display: flex; align-items: center; justify-content: center; gap: 18px; text-align: center; } +.section-heading .section-kicker { justify-content: center; } h2 { margin: 8px 0 0; font: 500 25px/1.1 var(--serif); } #queue-count { display: inline-grid; place-items: center; width: 23px; height: 23px; margin-left: 5px; border-radius: 50%; background: #e6e7df; color: var(--moss-dark); font: 11px var(--mono); vertical-align: 3px; } -.icon-button { width: 38px; height: 38px; border: 1px solid var(--line); background: transparent; color: var(--moss-dark); font-size: 22px; cursor: pointer; } +.icon-button { position: absolute; right: 0; width: 38px; height: 38px; border: 1px solid var(--line); background: transparent; color: var(--moss-dark); font-size: 22px; cursor: pointer; } .icon-button:disabled { opacity: .4; cursor: wait; } .queue-list, .archive-list { list-style: none; margin: 16px 0 0; padding: 0; } .queue-item, .archive-item { display: flex; align-items: flex-start; gap: 15px; padding: 14px 0; border-top: 1px solid #e3e4dd; animation: reveal .35s ease both; } diff --git a/frontend/sw.js b/frontend/sw.js index 0736243..cbe4709 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,5 +1,5 @@ -const CACHE_NAME = 'faerro-kb-shell-v1'; -const APP_SHELL = ['/', '/styles.css', '/app.js', '/manifest.webmanifest']; +const CACHE_NAME = 'faerro-kb-shell-v2'; +const APP_SHELL = ['/', '/styles.css', '/app.js', '/manifest.webmanifest', '/favicon.svg']; self.addEventListener('install', (event) => { event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL))); diff --git a/scripts/publish-image.sh b/scripts/publish-image.sh index 3556f4f..9c37ce7 100755 --- a/scripts/publish-image.sh +++ b/scripts/publish-image.sh @@ -3,6 +3,24 @@ set -euo pipefail image="gitea.faerro.it/andrea/faerro-kb" repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +bump="patch" + +while [[ $# -gt 0 ]]; do + case "$1" in + --major|--minor) + if [[ "$bump" != "patch" ]]; then + echo "Choose only one of --major or --minor." >&2 + exit 1 + fi + bump="${1#--}" + ;; + *) + echo "Usage: $0 [--major | --minor]" >&2 + exit 1 + ;; + esac + shift +done if ! command -v docker >/dev/null 2>&1; then echo "Docker is required to build and publish the image." >&2 @@ -14,14 +32,43 @@ if ! command -v git >/dev/null 2>&1; then exit 1 fi -tag="$(git -C "$repo_root" describe --tags --exact-match HEAD 2>/dev/null || true)" -version="${tag#v}" +latest_version="" +while IFS= read -r candidate; do + if [[ "$candidate" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then + latest_version="$candidate" + break + fi +done < <(git -C "$repo_root" tag --list --sort=-version:refname) -if [[ ! "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then - echo "The checked-out commit must have a SemVer tag (for example, 0.0.2 or v0.0.2)." >&2 +if [[ -z "$latest_version" ]]; then + echo "At least one MAJOR.MINOR.PATCH Git tag is required to determine the next version." >&2 exit 1 fi +IFS=. read -r major minor patch <<< "$latest_version" +case "$bump" in + major) + major=$((10#$major + 1)) + minor=0 + patch=0 + ;; + minor) + minor=$((10#$minor + 1)) + patch=0 + ;; + patch) + patch=$((10#$patch + 1)) + ;; +esac +version="$major.$minor.$patch" + +if git -C "$repo_root" show-ref --verify --quiet "refs/tags/$version"; then + echo "Git tag $version already exists; refusing to move it." >&2 + exit 1 +fi + +git -C "$repo_root" tag "$version" + cd "$repo_root" docker build --tag "$image:$version" --tag "$image:latest" . registry_username="${GITEA_USERNAME:-${DOCKER_USERNAME:-}}"