release: v1.17.0#295
Merged
Merged
Conversation
… (#280) The accent colour re-themes the whole UI but could only be set from the native macOS app — and docs/theming.md has falsely claimed a web control since v1.0. Add one, and make accent PER-THEME so it composes with the theme system. - Admin → Site settings → Appearance gains an accent picker (colour input + hex + "Use theme's accent"). It's bound to the selected theme and POSTs to update_profile (the profile overlay — same store /api/account + the AP actor read), not site-config, so there's one source of truth. - Storage: new `SiteSettings.themeAccents` Json column, `{ themeId: "#rrggbb" }`. The legacy `accentColor` stays the DEFAULT theme's accent (what the macOS app reads/writes), so this is additive + backward-compatible. `prisma generate` offline; `db push` adds the nullable column at deploy. - New pure `resolveAccent(themeId, profile)`: per-theme override wins; else the legacy accentColor for the default theme only (schema-default #3b82f6 treated as "unset" → inherit, so existing instances render identically); else null = inherit the theme's own accent. - Fix the silent-override trap: buildThemeStyle drops the DEFAULT_ACCENT no-op guard and overlays any resolved hex — the inherit-vs-custom decision now lives in resolveAccent. A custom accent no longer bleeds onto other themes, and blue is finally selectable on Editorial. - Federation guard: an accent-only save no longer blasts a byte-identical actor Update to every follower (accent isn't in the actor doc). Name/avatar/summary edits still federate. - /api/account exposes themeAccents; docs/theming.md corrected. Tests: resolveAccent (per-theme, legacy-default, inherit, junk); buildThemeStyle (overlays any hex, default inherit → ""); updateProfile (themeAccents merge + clear, unknown-theme reject, federation guard); site-profile themeAccents parse. 501 pass. Co-authored-by: Samuel Lison <samuel@slcolonies.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The generate-token reveal now also offers a `fedihome://connect?instance=…& token=…` link bundling the instance URL + token, so a native app (macOS/iOS) can onboard from a single copy instead of two separate fields — greasing the click-to-connect flow the apps are built around. - Instance URL comes from siteConfig.url (passed as a prop, since SITE_URL isn't in the client bundle); both params are encodeURIComponent-escaped. - Token stays reveal-once; the link is only shown in the same box, never persisted. - Documented in docs/app-api.md as a PROPOSED onboarding contract — flagged to confirm the scheme/params against the macOS/iOS app before it's stable. Co-authored-by: Samuel Lison <samuel@slcolonies.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…olour (#250) (#282) Themes could vary hue + type but not texture — globals.css hardcoded .glass-card blur(12px)/radius 12px and the button radius 8px. Add a `feel` group to ThemeTokens (radiusCard, radiusButton, glassFilter) surfaced as --radius-card / --radius-button / --glass-filter, referenced by those classes, with defaults declared at plain :root so buildThemeStyle's :root:root wins. - glassFilter is the WHOLE backdrop-filter (e.g. "blur(12px)" or "none"), so a theme can turn glass off — blur(0px) would still create a compositing layer. - default's feel equals the literals it replaced, so buildThemeStyle emits nothing for it → byte-identical. Editorial goes crisp/flat: 4px corners, glass-filter none — a print counterpoint to the rounded frosted glass. - The fedi-badge pill (9999px) stays hardcoded — it's intrinsic, not "feel". Verified: default injects "" (unchanged); editorial injects the 3 feel vars + renders visibly crisper (screenshotted). 502 tests. Co-authored-by: Samuel Lison <samuel@slcolonies.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… (#283) Another slice toward "configure everything from the web, not the server's .env". Adds contact.email + podcast.{title,author,description,email,image} to the shipped site-config overlay (SITE_CONFIG_FIELDS + RuntimeSiteConfig + validation + a "Contact & podcast" section in Admin → Site settings). Empty = derive from the profile, same as the env-var defaults. Also rewires src/app/audio/feed.xml to read getRuntimeSiteConfig() + getRuntimeProfile() instead of process.env / siteConfig directly — which fixes a latent bug: the podcast feed ignored a web-edited author name / site config and only reflected the env vars. Identity + secrets deliberately stay env-only (the deferred .env→DB migration). Tests extend site-settings.test.ts. 503 pass. Co-authored-by: Samuel Lison <samuel@slcolonies.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Gallery categories were hard-coded in 4 places (3 divergent lists) with no server validation, and the /videos + /audio galleries had no category filter at all. Make them web-editable via the shipped #59 overlay, with one source of truth. - New pure `src/lib/categories.ts`: DEFAULT_CATEGORIES (the old hard-coded values) + parse/resolve/label/union/tabs/normalize helpers. Client-safe. - Config overlay: categories.photos/videos/audio ("text" CSV → slug arrays), RuntimeSiteConfig.categories, validation (slug-only, dedupe, cap 24, ""= defaults), site.config.ts group. A Categories section in Site settings. - Compose: the 3 dropdowns read the resolved lists (passed from the server page); write path uses normalizeCategory (slug-safe). - Galleries: /photography reads the list; NEW VideoGrid + AudioGrid add the filter tabs those galleries never had (shared CategoryTabs, extracted from PhotoGrid). Each gallery's tabs = configured list ∪ categories still in the DB, so removing a category never hides existing media (orphan safety). - Labels auto-title-case the slug ("photo-walk" → "Photo Walk"); "general" is always guaranteed. Softened hard-coded category names in gallery copy. Tests: categories.test.ts (13) + site-settings overlay/validation. 516 pass. No local DB, so gallery rendering is verified via next build + the pure-helper logic tests (noted); union-with-DB is unit-tested via unionCategories. Co-authored-by: Samuel Lison <samuel@slcolonies.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The admin theme picker shipped, but the setup wizard had no theme step. Add an Appearance block (Theme + feed layout selects) to the wizard's Site Features step — backend unchanged, since theme.id/layout.feed are already valid site-config keys written via the wizard's existing applySiteConfig path. Theme options come from the THEMES registry; only non-default choices are sent as overrides. Folded into the existing step (no step-count renumber). A fresh install now lands on the owner's chosen look with no file editing. Verified: build clean; eyeballed the Appearance selects in the wizard step 5. Co-authored-by: Samuel Lison <samuel@slcolonies.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Move Tinylytics' public embed ids web-side: analytics.siteId + analytics.embedId as plain site-config text fields (RuntimeSiteConfig.analytics), an "Analytics" section in Site settings, and rewire the Tinylytics embed to take the resolved siteCode as a prop from layout.tsx (which already resolves runtime config). Collection now starts with no .env editing or restart. Also: the CSP allow-list for tinylytics.app was env-gated, which would have silently blocked a web-configured id (headers() is static and can't read the DB config). Made it unconditional — the domain is only ever contacted when the embed renders, so it's inert when analytics is off. Scope: the in-app dashboard/kudos/footer counter go through the API-KEY path (lib/tinylytics.ts), which stays env-only until an encrypted-secret panel lands. So this slice is collection-only, by design. Tests: analytics overlay in site-settings; the embed test moves to the prop signature. 516 pass. Co-authored-by: Samuel Lison <samuel@slcolonies.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…y works (#288) (#289) The on-page collecting embed loads tinylytics.app/embed/<code>.js, and that endpoint needs the site's `uid` — NOT the numeric TINYLYTICS_SITE_ID. With only the numeric id set, the embed was `…/embed/3461.js` → 404 → zero pageviews collected, silently, while the dashboard (which correctly uses the numeric id for the API) looked fine. - resolveTinylyticsEmbed (src/lib/tinylytics.ts): explicit embedId override → a site id that's already a uid (non-numeric) used as-is → a numeric site id derived to its uid via GET /sites/{id} (needs the API key). Never emits a numeric id (it 404s). getSiteUid caches the immutable uid in-memory (this runs on the root-layout render path), with a short negative TTL so it self-heals, a 4s timeout so it never blocks the render, and a console warning when it can't resolve. - layout.tsx renders the resolved code (nothing when unresolvable, vs a broken 404ing script before). Works from web-config or env — a numeric site id + API key "just works", no manual uid lookup (addresses the non-technical / web-settings case). - Admin → Site settings → Analytics surfaces it: "✓ Collecting" with the embed code, or "⚠️ not collecting" with how to fix — live-refreshed from the save response (the site-config route now returns the resolved status). Clarified the field labels + the misleading .env.example copy. Tests: resolveTinylyticsEmbed across all paths (override / uid passthrough / derive+cache / no-key / 404 / unset). 523 pass. Co-authored-by: Samuel Lison <samuel@slcolonies.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
#288 follow-up) (#290) Verified on the demo: the auto-derive path collects site-wide. Two rough edges from the build reported there: - The build-time warning claimed "pageviews are NOT being collected" during `next build`, even though dynamic pages resolve the uid at request time and collection is fine — alarming in build logs. Reworded to be phase-aware and honest (transient at build; the scary claim is reserved for a real runtime miss). The admin panel remains the authoritative surface. - `next build` renders across parallel workers, each with a cold uid cache, so concurrent /sites/{id} lookups can race the 4s timeout / get rate-limited. If a route were ever statically generated, a lost race would bake an empty embed into it — the exact silent-non-collection this fixes, per page. Hardened the build lookup with an 8s timeout + one retry (runtime stays a short 4s single shot), and a build-time miss is no longer cached (the server retries fresh). Test: build-phase retry (first call fails, retry succeeds). 524 pass. Co-authored-by: Samuel Lison <samuel@slcolonies.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…config (#284) (#291) Reopened #284: the web side of customisable gallery categories shipped in #285, but the one item the native Mac app needs was missing — a canonical source of truth for the photo/video/audio categories on the app API. Without it a client can only derive categories by paging existing posts, so a configured-but-unused category (e.g. a freshly-added "landscape" with nothing in it yet) is invisible. GET /api/micropub?q=config now also returns: "mediaCategories": { "photos": [{ "slug": "wildlife", "label": "Wildlife" }, ...], "videos": [...], "audio": [...] } - Same union the galleries render: the owner's configured list ∪ the categories still in use (published only), via the shared unionCategories helper — so a removed-but-still-used category never disappears (issue decision 1). - Structured { slug, label } (issue decision 4 / app comment item 4) — clients show the label, submit the slug, and don't re-implement title-casing. Reserves the shape for a future slug|Label without a breaking change. - Distinct `mediaCategories` key, separate from the post-type `categories` array (no collision). - Compose writes are untouched — still lenient (issue decision 3): free-typing a brand-new category keeps working for web and the app. - Per-media failure isolation: the three in-use lookups settle independently, so one media type's DB hiccup degrades only its own list to configured-only (the endpoint is unauthenticated and polled — it must not 500). New pure helper categoryEntries(configured, present) in @/lib/categories. Tests: categoryEntries units + a micropub-config route test (structured shape, published-only filter, per-media failure isolation). 529 pass. Unblocks TemujinCalidius/FediHome-macOS#61. Co-authored-by: Samuel Lison <samuel@slcolonies.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ost reader (#292) (#293) The native "My Posts" manager could only show the ~200-char `preview`, so the owner couldn't read a post in full from the app. `q=source` returns markdown (for the editor) and the app has no markdown renderer — it renders sanitized HTML via its existing FediHTML formatter, the same path the feed uses. The Prisma `select` already fetched `contentHtml` (read only to derive `preview`), so this just returns it on each post — `string | null`, as-is. Drafts + scheduled posts are included exactly as before. 20 posts of HTML per page is bounded and fine for a single-user manager (no pagination change). Documented in docs/app-api.md; test asserts the field ships (value + null). Unblocks TemujinCalidius/FediHome-macOS#65. Co-authored-by: Samuel Lison <samuel@slcolonies.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Convert the Unreleased block to 1.17.0 (2026-07-19) and bump package.json + lock, via scripts/prepare-release.mjs. Ships the accumulated dev batch: web accent (#276), feel/texture tokens + setup theme step (#250), web-editable gallery categories + q=config mediaCategories (#284), contact/podcast + analytics config (#59), one-paste app sign-in link (#255), contentHtml on /api/posts (#292), and the Tinylytics embed-uid fix (#288). Co-authored-by: Samuel Lison <samuel@slcolonies.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v1.17.0 — merge
dev→main(a true merge, not squash, so history is shared).Release notes go on the GitHub Release + Discussions announcement. Highlights:
q=configasmediaCategoriesfedihome://connectapp sign-in link (Admin: generate scoped, reveal-once app tokens #255) ·contentHtmlonGET /api/postsfor the in-app full-post reader (Add contentHtml to GET /api/posts (for the macOS My-Posts full-post view) #292)🤖 Generated with Claude Code