Signed by hannaSSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
docs/decisions.md +29 −0
| @@ -555,3 +555,32 @@ re-exports `ssh-key` at a patch level distinct from the workspace's, so the two | |||
| 555 | 555 | by key fingerprint, not the SSH username (everyone connects as `git@`); | |
| 556 | 556 | `channel_open_session` must explicitly `reply.accept()` in 0.62 or the channel is | |
| 557 | 557 | rejected; exec output streams through the `Clone + Send` `Handle` from a task. | |
| 558 | + | ||
| 559 | + | ## 2026-07-25 — UI refresh: inline icons, Carbon themes, dashboard/explore split, profiles | |
| 560 | + | ||
| 561 | + | **Decision:** Vendor UI glyphs as an inline-SVG `icons` module (Lucide, ISC) with | |
| 562 | + | brand marks from Simple Icons (CC0), replacing the emoji glyphs. Rebase the default | |
| 563 | + | `dark`/`light` themes on IBM Carbon (Gray 100 / White) as pure `--fb-*` token | |
| 564 | + | changes. Split `/` into a signed-in dashboard and a public `/explore` landing (the | |
| 565 | + | anonymous landing carries no in-content sign-in button). Give users a profile | |
| 566 | + | (display name, pronouns, bio, location, website, GitHub/Mastodon handles) edited at | |
| 567 | + | `/settings`, and avatars served from `/avatar/{username}`. | |
| 568 | + | ||
| 569 | + | **Avatar storage:** the bytes live on disk at `{storage.data_dir}/avatars/{id}`; | |
| 570 | + | `users.avatar_mime` (nullable TEXT, migration 0002) records the content type and its | |
| 571 | + | presence means "serve the file". A NULL falls back to a deterministic FNV-derived | |
| 572 | + | 5×5 identicon SVG generated in-process. | |
| 573 | + | ||
| 574 | + | **Alternatives:** an icon font or sprite sheet (extra asset, worse a11y); storing | |
| 575 | + | avatar bytes in the DB (BLOB vs BYTEA is not portable across SQLite/Postgres, which | |
| 576 | + | § store portability forbids); Gravatar or any remote avatar (a network request from | |
| 577 | + | a page, forbidden by §9.5); a single combined home page (the prior behaviour). | |
| 578 | + | ||
| 579 | + | **Rationale:** inline SVG inherits `currentColor`, scales to `1em`, needs no network | |
| 580 | + | request, and keeps the "no CDN / no build step" rule. Carbon is a documented, freely | |
| 581 | + | usable palette and the swap touches only theme token values, proving the | |
| 582 | + | structure-only `base.css` contract. On-disk avatars keep the schema in the portable | |
| 583 | + | subset. Uploads are limited to raster types (PNG/JPEG/GIF/WebP, ≤ 1 MiB); SVG upload | |
| 584 | + | is refused so no user-supplied markup is ever served, while our own identicons are | |
| 585 | + | SVG. Both mutating routes (`POST /settings`, `POST /settings/avatar`) verify the | |
| 586 | + | double-submit CSRF token; `/settings` redirects anonymous visitors to `/login`. | |