feat: rename service to galo, add halo branding and release build

- rename Go module, binary, systemd unit, PG_DATABASE default, frontend package and docs from shared-album to galo
- add theme-aware halo emblem and favicon (new ui-branding capability)
- add make build-release (versioned, stripped, static binary) and run the binary from the repo root
This commit is contained in:
iu
2026-10-11 11:36:56 +03:00
parent 29c2594a9b
commit 50dff63ece
89 changed files with 696 additions and 254 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-11
@@ -0,0 +1,49 @@
# Design
## Context
See proposal.md - Why. Current state that shapes the approach:
- `frontend/src/app.html:6` already points at `%sveltekit.assets%/favicon.png`, but `frontend/static/` does not exist, so the request 404s.
- The icon system (`ui-iconography`) requires self-contained inline vector icons with no external requests, and icons that inherit the theme color.
- Theme colors live as CSS custom properties on `:root` per theme (`--primary` = blue light/dark, orange neutral), defined in `frontend/src/app.css`.
- Static assets flow through `frontend/static/` -> `frontend/build/` -> `backend/static/` -> `go:embed` -> served by the static handler. No backend change is needed.
## Goals / Non-Goals
**Goals:**
- One recognizable halo mark, legible down to 16px, used consistently as UI emblem and favicon.
- Theme-awareness in both the UI emblem and the browser/touch icon.
- Zero external requests for any brand asset.
**Non-Goals:**
- Changing the wordmark, typography, or theme palette.
- Open Graph / social preview images, PWA manifest icons, or app-store assets.
- Any backend or API change.
## Decisions
### D1. Concept: pure halo (variant A)
A sun disc with a separated outer ring. Alternatives: rays/glow (rejected - detail collapses at 16px) and an aperture hybrid (rejected - two meanings compete at small sizes). Geometry keeps a clear gap between disc and ring so the halo reads as a halo.
### D2. Two-tone color with monochrome fallback
In the UI, the disc uses `--primary` and the ring uses `--primary` at reduced opacity; a single-color `currentColor` fallback keeps the mark usable where the two-tone treatment does not apply. Alternative: strictly monochrome (rejected - loses the "sun inside a halo" reading).
### D3. Inline SVG for UI, static files for favicon
The UI emblem is an inline SVG component so it inherits `currentColor`/theme and satisfies the no-external-requests rule, mirroring the existing icon approach. The favicon cannot use CSS custom properties, so it is a standalone asset that carries its own theme rules.
### D4. Favicon formats
Ship `favicon.svg` with an embedded `prefers-color-scheme` rule (dark and light variants), plus `favicon.png` and `apple-touch-icon.png` fallbacks. `app.html` references the SVG and keeps a PNG fallback. Alternative: `.ico` only (rejected - no theme awareness).
### D5. Placement
Emblem in the `AppShell` header, the public/login layout, and the share-page header, always next to the "Galo" wordmark, where it is decorative (hidden from assistive tech).
### D6. Asset pipeline
Files live in `frontend/static/`; the existing `make frontend` copy step carries them into `backend/static/` for embedding, so the backend serves them unchanged.
## Risks / Trade-offs
- [Mark illegible at 16px] -> Keep geometry minimal (thick-enough ring, clear gap) and verify a 16px render.
- [SVG favicon not supported everywhere] -> Provide the PNG fallback in `app.html`.
- [Favicon cannot use theme CSS variables] -> Embed `prefers-color-scheme` rules directly in the SVG.
- [Asset bloat] -> Keep the SVG hand-written and small; PNGs limited to the needed sizes.
@@ -0,0 +1,27 @@
# Proposal
## Why
The app references a favicon that does not exist (`frontend/src/app.html:6` points at `%sveltekit.assets%/favicon.png`, but there is no `frontend/static/`), so browsers get a 404. At the same time the product name **Galo** (= гало, the ring around the sun) has no visual mark — the brand is only text. This change gives the project a self-hosted halo emblem and a working favicon, consistent with the existing icon system that forbids external requests.
## What Changes
- Introduce a **halo emblem**: a sun disc surrounded by a separated ring (variant A — pure ring + disc, legible at 16px).
- Add an inline, theme-aware SVG emblem component used in the UI (header, login/public layout, share header); the sun disc uses `--primary`, the ring uses `--primary` at reduced opacity with a monochrome `currentColor` fallback.
- Add static brand assets under `frontend/static/`: `favicon.svg` (theme-aware via `prefers-color-scheme`), `favicon.png` fallback, and `apple-touch-icon.png`.
- Update `frontend/src/app.html` to reference `favicon.svg` with the PNG fallback.
- Keep the "Galo" wordmark next to the emblem; the emblem is decorative there.
## Capabilities
### New Capabilities
- `ui-branding`: the halo emblem and favicon identity — a self-hosted, theme-aware visual mark that appears in the UI and as the browser/tab and touch icon.
### Modified Capabilities
<!-- None: the icon system (ui-iconography) is unchanged; the emblem follows its existing no-external-requests rule. -->
## Impact
- **Frontend:** new `frontend/static/` assets (served through the existing `go:embed` pipeline after build), `frontend/src/app.html`, `frontend/src/lib/components/AppShell.svelte`, `frontend/src/routes/(public)/+layout.svelte`, share page header.
- **Backend:** none (static assets are embedded and served by the existing static handler).
- **Behavior:** the missing favicon 404 is replaced by a served, theme-aware icon; no API changes.
@@ -0,0 +1,62 @@
# Spec Delta
## Purpose
Единая визуальная айдентика приложения: гало-эмблема и фавикон, отдаваемые самим приложением, наследующие тему и читаемые в мелком размере.
## ADDED Requirements
### Requirement: Гало-эмблема
Интерфейс SHALL отображать эмблему «гало» — диск солнца, окружённый отделённым от него кольцом — как встроенную векторную графику, наследующую цвет текущей темы.
#### Scenario: Отображение в шапке
- **WHEN** открыт основной интерфейс
- **THEN** в шапке рядом с надписью «Galo» показана гало-эмблема
#### Scenario: Смена темы
- **WHEN** пользователь переключает тему (light/dark/neutral)
- **THEN** цвет диска и кольца эмблемы меняется вместе с темой
#### Scenario: Мелкий размер
- **WHEN** эмблема отрисована в размере порядка 16-24 px
- **THEN** диск и отделённое кольцо остаются различимыми
### Requirement: Фавикон от приложения
Приложение SHALL отдавать фавикон бренда со своего origin, а разметка страницы SHALL ссылаться на существующий файл.
#### Scenario: Запрос фавикона
- **WHEN** браузер запрашивает фавикон
- **THEN** приложение отвечает изображением SVG с собственным `Content-Type`
#### Scenario: Запасной формат
- **WHEN** клиент не поддерживает SVG-фавикон
- **THEN** разметка предоставляет PNG-запасной вариант
### Requirement: Фавикон под тему
Фавикон SHALL учитывать предпочтение схемы оформления и в тёмной схеме SHALL использовать светлый вариант эмблемы на тёмном фоне.
#### Scenario: Тёмная схема
- **WHEN** система сообщает о предпочтении тёмной схемы
- **THEN** фавикон отображается в варианте для тёмной схемы
#### Scenario: Светлая схема
- **WHEN** система сообщает о предпочтении светлой схемы
- **THEN** фавикон отображается в варианте для светлой схемы
### Requirement: Доступность эмблемы
Эмблема SHALL быть декоративной рядом с текстовым вордмарком и SHALL иметь доступное имя, когда используется как самостоятельный смысловой элемент.
#### Scenario: Эмблема рядом с вордмарком
- **WHEN** эмблема сопровождает видимый текст «Galo»
- **THEN** она скрыта от вспомогательных технологий
#### Scenario: Самостоятельная эмблема
- **WHEN** эмблема является единственным содержимым управляющего элемента
- **THEN** элемент имеет доступное имя
### Requirement: Автономность ассетов бренда
Эмблема и фавикон SHALL загружаться с самого приложения и SHALL NOT требовать запросов к внешним источникам.
#### Scenario: Загрузка без внешней сети
- **WHEN** страница загружается без доступа к внешней сети, кроме своего сервера
- **THEN** эмблема и фавикон отображаются
@@ -0,0 +1,26 @@
# Tasks
## 1. Brand assets
- [x] 1.1 Create `frontend/static/favicon.svg` with the halo mark (sun disc + separated ring) and embedded `prefers-color-scheme` dark/light variants, and verify it opens as valid SVG and renders legibly at 16px
- [x] 1.2 Create `frontend/static/favicon.png` (fallback) and `frontend/static/apple-touch-icon.png` rendered from the same emblem, and verify both files exist at their intended pixel sizes (32x32 and 180x180)
- [x] 1.3 Update `frontend/src/app.html` to reference `favicon.svg` with the PNG as fallback, and verify `make frontend` copies both into `frontend/build/`
## 2. UI emblem
- [x] 2.1 Add an inline SVG emblem component (disc in `--primary`, ring in `--primary` at reduced opacity, monochrome `currentColor` fallback), and verify it renders at 16-24px with the ring and disc still distinct
- [x] 2.2 Verify the emblem changes color with the theme by switching light/dark/neutral and confirming the disc and ring follow the active theme
## 3. Placement
- [x] 3.1 Show the emblem in the `AppShell` header next to the "Galo" wordmark and verify it appears in the main interface
- [x] 3.2 Show the emblem in the public/login layout and on the share page header and verify it appears on both
## 4. Accessibility
- [x] 4.1 Verify every decorative emblem (next to the wordmark) is hidden from assistive technologies and any standalone emblem element has an accessible name
## 5. Integration verification
- [x] 5.1 Run `make build` and verify the served favicon responds as an SVG image (`curl -I` shows the SVG content type) and that no brand asset request returns 404
- [x] 5.2 Validate the change and verify `openspec validate add-halo-branding` passes
@@ -0,0 +1,3 @@
schema: spec-driven
created: 2026-10-11
skip_specs: true
@@ -0,0 +1,56 @@
# Design
## Context
See proposal.md - Why. Current state that shapes the approach:
- The Go module is `shared-album` (`backend/go.mod:1`), imported as `shared-album/internal/*` by ~63 files and as `assets "shared-album"` by 9 of them (module-root `backend` package holding `go:embed`).
- The name also appears in the binary target (`Makefile`), the systemd unit and its `/opt/shared-album` paths, the nginx upstream, the frontend package name, and the docs.
- The real deployment `.env` already uses `PG_DATABASE=galo` and `S3_BUCKET=galo-data`; only the code/sample defaults are stale.
- Generated artifacts (`bin/`, `frontend/build/`, `frontend/.svelte-kit/`) are gitignored and pick up the new name from the build.
- No user-facing string literal carries the old name: the UI already renders "Galo".
## Goals / Non-Goals
**Goals:**
- One consistent name `galo` across Go module, build target, deploy unit, config defaults, frontend package, and docs.
- Preserve behavior except the intended DB-name default.
- Keep the change verifiable by the existing build/test/validate commands.
**Non-Goals:**
- Renaming the immutable archived change `openspec/changes/archive/2026-10-10-shared-album`.
- Migrating existing database contents (the default is config-driven; live `.env` is already `galo`).
- Any UI/branding change (already "Galo").
## Decisions
### D1. Rename the Go module to `galo`
Flat path, matching the current style (`module shared-album`, no domain). Alternatives: keep the module name (rejected - stays inconsistent) or adopt a namespaced path like `github.com/.../galo` (rejected - no hosting/import need, larger churn).
Impact: replace `shared-album` in `backend/go.mod` and every import/alias across the backend, then rely on the compiler (`go build ./...`) to prove completeness.
### D2. Change the metadata DB-name default to `galo`
Update `config.go` default and `.env.example`. Rationale: aligns the code with the live `.env` (`PG_DATABASE=galo`). Alternative: leave the default (rejected - keeps the stale name). Trade-off: installs that omit `PG_DATABASE` will now target `galo`; deployments should set it explicitly.
### D3. Rename the deploy unit and install path
`deploy/shared-album.service` -> `galo.service`, `/opt/shared-album` -> `/opt/galo`, and the nginx upstream `shared_album` -> `galo`. This is an operational change requiring re-install on the server; it stays a single unit with the same flags and `EnvironmentFile`.
### D4. Leave history intact
The archived OpenSpec change keeps its original name - it records what happened, not what the project is called now.
### D5. Frontend package rename
`shared-album-frontend` -> `galo-frontend` in `package.json`; regenerate `package-lock.json` via install so the lock name matches.
## Risks / Trade-offs
- [Missed or mistyped import path] -> Compiler-enforced; do the module/import replacement first, then `go build ./... && go test ./...`.
- [Deploy breaks on path/unit rename] -> Treat as a documented migration step: stage `/opt/galo`, re-install the unit, disable the old one; keep `.env` authoritative.
- [DB-name default surprises a fresh install] -> `.env.example` and docs state it; recommend keeping `PG_DATABASE` explicit in every `.env`.
- [Lockfile drift after package rename] -> Regenerate the lock rather than hand-editing only its `name` fields.
## Migration Plan
1. Land the rename in one commit: backend module + imports, Makefile, `config.go`/`.env.example`, `deploy/*`, frontend package, docs.
2. Verify: `cd backend && go build ./... && go test ./...`, `make build`, `openspec validate rename-shared-album-to-galo`.
3. On the server: create `/opt/galo`, place `galo` binary and `.env`, install `galo.service`, `systemctl disable --now shared-album`, then `enable --now galo`.
4. Rollback: revert the commit and re-enable the old unit; no data migration is involved.
@@ -0,0 +1,33 @@
# Proposal
## Why
The product is named **galo** — the UI already shows "Galo" and the frontend persists state under `galo.*` keys — yet internal identifiers, build artifacts, the deploy unit, and the docs still carry the old working name `shared-album`. This mismatch confuses operators and readers, and the stale metadata default `shared_album` in `config.go` diverges from the actual deployment, whose `.env` already sets `PG_DATABASE=galo`.
## What Changes
- Rename the Go module `shared-album` -> `galo` and update every internal import path (`shared-album/internal/...`, `assets "shared-album"`) across the backend (~63 files).
- Rename the built binary `bin/shared-album` -> `bin/galo` in the Makefile.
- Rename the systemd unit `deploy/shared-album.service` -> `deploy/galo.service` and its install path `/opt/shared-album` -> `/opt/galo`.
- Change the metadata-layer DB name default `shared_album` -> `galo` in `config.go` and `.env.example`, aligning the default with the real deployment.
- Rename the nginx upstream `shared_album` -> `galo`.
- Rename the frontend package `shared-album-frontend` -> `galo-frontend` (package.json + regenerated lock).
- **BREAKING** (operational): the deploy unit name and install path change, so an existing server needs the unit re-installed and the path re-created.
- Update docs (`project.md`, including the §18 prompt log, and `README.md`) to the new name.
- Leave immutable history untouched: the archived change `openspec/changes/archive/2026-10-10-shared-album` keeps its name.
## Capabilities
### New Capabilities
<!-- None: this is a pure identifier/refactor rename with no new behavior. -->
### Modified Capabilities
<!-- None: no spec-level behavior changes. The change's .openspec.yaml must set skip_specs: true. -->
## Impact
- **Backend:** `backend/go.mod` module path + all `shared-album/internal/...` imports (~63 Go files); `internal/config/config.go` DB default.
- **Build/deploy:** `Makefile`, `deploy/shared-album.service` (renamed), `deploy/nginx-sse.conf`, `.env.example`.
- **Frontend:** `frontend/package.json` + `package-lock.json` (name only).
- **Docs:** `project.md`, `README.md`.
- **Behavior:** none at the API level; the only runtime-visible change is the default `PG_DATABASE` (affects fresh installs that omit the variable — the current `.env` already uses `galo`). Operational change: systemd unit/path rename.
@@ -0,0 +1,35 @@
# Tasks
## 1. Go module and imports
- [x] 1.1 Rename the module in `backend/go.mod` from `shared-album` to `galo` and verify `head -1 backend/go.mod` prints `module galo`
- [x] 1.2 Replace every `shared-album/internal/...` import and the `assets "shared-album"` alias across the backend (~63 files) and verify `grep -rn '"shared-album' backend --include='*.go'` returns nothing
- [x] 1.3 Build and test the backend after the rename and verify `cd backend && go build ./... && go test ./...` passes
## 2. Build target
- [x] 2.1 Rename the binary output in the Makefile (`bin/shared-album` -> `bin/galo`) in both the `backend` and `run` targets and verify `make backend && file bin/galo` reports a statically linked `galo` binary
## 3. Config defaults
- [x] 3.1 Change the `PG_DATABASE` default in `backend/internal/config/config.go` and in `.env.example` from `shared_album` to `galo` and verify `grep -rn shared_album backend/internal/config .env.example` returns nothing while `cd backend && go test ./internal/config/` passes
## 4. Deploy artifacts
- [x] 4.1 Rename `deploy/shared-album.service` to `deploy/galo.service` and update its `WorkingDirectory`, `EnvironmentFile`, `ExecStart`, and `ReadWritePaths` paths from `/opt/shared-album` to `/opt/galo`, then verify `grep -rn shared-album deploy/` returns nothing
- [x] 4.2 Rename the nginx upstream `shared_album` to `galo` in `deploy/nginx-sse.conf` (upstream block and all `proxy_pass`) and verify `grep -rn 'shared_album\|shared-album' deploy/` returns nothing
## 5. Frontend package
- [x] 5.1 Rename the package to `galo-frontend` in `frontend/package.json` and regenerate `frontend/package-lock.json`, then verify `grep -n '"name"' frontend/package.json frontend/package-lock.json` shows `galo-frontend` everywhere
## 6. Documentation
- [x] 6.1 Update `README.md` and `project.md` (including the §18 step-by-step prompt log) to use `galo` / `/opt/galo` / `galo.service`, and verify `grep -rn shared-album README.md project.md` returns nothing
- [x] 6.2 Confirm the archived change is left untouched and verify `git status --porcelain openspec/changes/archive/` prints nothing
## 7. Integration verification
- [x] 7.1 Run a repo-wide audit and verify `grep -rn 'shared-album\|shared_album\|shared-album-frontend' --exclude-dir=.git --exclude-dir=bin --exclude-dir=node_modules --exclude-dir=build --exclude-dir=.svelte-kit .` returns hits only under `openspec/changes/archive/2026-10-10-shared-album/` and this change's own directory `openspec/changes/rename-shared-album-to-galo/`
- [x] 7.2 Run the full pipeline and verify `make build && make test` succeeds on the renamed codebase
- [x] 7.3 Validate the change and verify `openspec validate rename-shared-album-to-galo` passes