Enhance documentation, update release process, and improve UI elements
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
+1
-2
@@ -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();
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<circle cx="32" cy="32" r="32" fill="#355a43" />
|
||||
<text x="32" y="45" fill="#fffefa" font-family="Georgia, serif" font-size="40" text-anchor="middle">V</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 229 B |
+3
-3
@@ -7,24 +7,24 @@
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<title>Faerro KB</title>
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<a class="brand" href="/" aria-label="Faerro KB"><span class="brand-mark">V</span><span>voice <b>kb</b></span></a>
|
||||
<a class="brand" href="/" aria-label="Faerro KB"><span class="brand-mark">V</span><span>Faerro <b>KB</b></span></a>
|
||||
<span id="network-state" class="network-state"><i></i> Checking</span>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="capture" aria-labelledby="capture-title">
|
||||
<div class="section-kicker"><span>PERSONAL ARCHIVE</span><span id="date-label"></span></div>
|
||||
<div class="recorder-row">
|
||||
<button id="record-button" class="record-button" type="button" aria-label="Start recording">
|
||||
<span class="record-dot"></span>
|
||||
</button>
|
||||
<div class="record-copy">
|
||||
<strong id="record-title">Ready when you are</strong>
|
||||
<strong id="record-title">Ready</strong>
|
||||
<span id="record-help">Tap to record a voice note</span>
|
||||
</div>
|
||||
<time id="record-time">00:00</time>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
+6
-6
@@ -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; }
|
||||
|
||||
+2
-2
@@ -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)));
|
||||
|
||||
@@ -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:-}}"
|
||||
|
||||
Reference in New Issue
Block a user