downloads: five clients, and the page leads with the one that fits you
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
Android / Kotlin + Rust (APK) (push) Skipped
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 11s
CI & Build / integration (push) Successful in 19s
CI & Build / Build & push image (push) Successful in 36s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m21s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m21s
Desktop (Tauri) / Update manifest (push) Successful in 5s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
Android / Kotlin + Rust (APK) (push) Skipped
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 11s
CI & Build / integration (push) Successful in 19s
CI & Build / Build & push image (push) Successful in 36s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m21s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m21s
Desktop (Tauri) / Update manifest (push) Successful in 5s
The Account page offered the APK and nothing else, because the APK was all the server held. Step 3 baked in four more, so the single card had to become a section — and five artifacts is exactly where a downloads page turns into a table of filenames and stops being a product. So it LEADS with what fits the machine asking, from the user agent, and keeps the rest quiet but visible. A wrong guess costs nothing: nothing is behind a disclosure and every other client is one click away. Linux gets all three at once, because the UA says "Linux" and nothing about dpkg or pacman — there is no better answer available. They are named for the distro rather than the package format, since a person knows which system they run and not necessarily which packaging it uses. The AppImage carries one clause of its own: it is 95 MB against 3, and it is also the only bundle that updates itself in place. Both facts belong to the same decision. macOS and iOS lead with nothing and say so. There is no build for either, and "There's no macOS build yet" is the difference between deliberate and broken. The version renders `unknown` rather than blank, and the download stays offered — not knowing which build it is, is not a reason to withhold it. Two things this did NOT do, both deliberate: The task asked for a Tauri case — do not offer the desktop app to someone already running it. That case cannot be reached: `/account` redirects to the board in the desktop app (requiresServer, router/index.ts), because device tokens are a server-side concept. A branch for it would be dead code. `.btn-link` mirrors BaseButton's declarations rather than replacing them. BaseButton is a <button> and cannot carry an href, and unifying the two would have put every button in the app into an operator pass that CI cannot check — for a cosmetic gain. The comment names the pair. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K3MMqUtzX1TJgA1oypvm1c
This commit is contained in:
@@ -2,15 +2,38 @@ import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { repo } from "../adapters";
|
||||
|
||||
// The Android build this server can hand out. Absent — not null — when it has
|
||||
// none, so `v-if` on it is the whole test; see client_dist.py.
|
||||
export interface AndroidClient {
|
||||
// One client build this server can hand out. Platforms it holds nothing for are
|
||||
// ABSENT from the map rather than present-and-null, so a key test is the whole
|
||||
// question; see client_dist.py.
|
||||
//
|
||||
// Named for its twin in `core/src/sync/client.rs`, which deserializes the same
|
||||
// payload. That one is deliberately NARROWER — it only ever reads
|
||||
// `/api/client/android`, so its `version_code` is an `i64` and it declares none of
|
||||
// the fields below that Android does not use. Widening it to match this is not a
|
||||
// tidy-up: every phone in the field runs the current shape.
|
||||
export interface ClientRelease {
|
||||
// The table row's id — "android", "linux-deb", "linux-appimage", "windows".
|
||||
platform: string;
|
||||
// What a person calls it, named for the DISTRO rather than the package format
|
||||
// ("Debian / Ubuntu", not ".deb"). The server owns this wording so the five
|
||||
// labels cannot drift apart across the surfaces that show them.
|
||||
label: string;
|
||||
version: string;
|
||||
// What decides "is this newer". The name is for people and sorts like a string.
|
||||
version_code: number;
|
||||
//
|
||||
// Not one type across platforms, deliberately: Android's is an integer because
|
||||
// Android's own install gate compares one, and the desktop's is Tauri's semver
|
||||
// key `1.0.<minutes>`. Nothing in this app compares them — the union is here so
|
||||
// the shape is honest rather than to be read.
|
||||
version_code: number | string;
|
||||
size: number;
|
||||
sha256: string;
|
||||
// A PATH, never an absolute URL — the client joins it to the server it is
|
||||
// already talking to.
|
||||
url: string;
|
||||
// Present only for the AppImage: the minisign signature the desktop updater
|
||||
// checks before replacing the running binary.
|
||||
signature?: string;
|
||||
}
|
||||
|
||||
export interface PublicConfig {
|
||||
@@ -20,7 +43,14 @@ export interface PublicConfig {
|
||||
enable_url_unfurl: boolean;
|
||||
// How many days a note survives in Trash before the server purges it. 0 = forever.
|
||||
trash_retention_days: number;
|
||||
android_client?: AndroidClient;
|
||||
// Every client this server holds, keyed by platform id. Absent on a server that
|
||||
// holds none, and absent on the desktop's own offline config — the Tauri build
|
||||
// answers `config_get` locally and has no clients to hand out.
|
||||
//
|
||||
// `/api/config` also carries `android_client`, which is NOT declared here: it
|
||||
// exists for phones in the field polling for their own update, not for this app,
|
||||
// and reading it here would be a second path to the same fact.
|
||||
clients?: Record<string, ClientRelease>;
|
||||
}
|
||||
|
||||
// Public, unauthenticated app config (site name, whether signups are open).
|
||||
@@ -33,9 +63,9 @@ export const useConfigStore = defineStore("config", () => {
|
||||
// unreachable — and 30 is a safer stand-in than 0, since claiming "kept forever"
|
||||
// when the server is actually purging is the wrong way to be wrong.
|
||||
const trashRetentionDays = ref(30);
|
||||
// Null until proven otherwise: a server with no APK, and an older server that
|
||||
// never had the field, both correctly show no download.
|
||||
const androidClient = ref<AndroidClient | null>(null);
|
||||
// Empty until proven otherwise: a server with no clients, and an older server
|
||||
// that never had the field, both correctly offer no downloads.
|
||||
const clients = ref<Record<string, ClientRelease>>({});
|
||||
const loaded = ref(false);
|
||||
|
||||
async function load(): Promise<void> {
|
||||
@@ -47,7 +77,7 @@ export const useConfigStore = defineStore("config", () => {
|
||||
version.value = cfg.version;
|
||||
enableUrlUnfurl.value = cfg.enable_url_unfurl ?? true;
|
||||
trashRetentionDays.value = cfg.trash_retention_days ?? 30;
|
||||
androidClient.value = cfg.android_client ?? null;
|
||||
clients.value = cfg.clients ?? {};
|
||||
} catch {
|
||||
// Keep defaults if the config endpoint is unreachable.
|
||||
} finally {
|
||||
@@ -66,7 +96,7 @@ export const useConfigStore = defineStore("config", () => {
|
||||
version,
|
||||
enableUrlUnfurl,
|
||||
trashRetentionDays,
|
||||
androidClient,
|
||||
clients,
|
||||
loaded,
|
||||
load,
|
||||
reload,
|
||||
|
||||
Reference in New Issue
Block a user