Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 3s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 14s
CI & Build / integration (push) Successful in 15s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m50s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m19s
Desktop (Tauri) / Update manifest (push) Successful in 5s
Android / Kotlin + Rust (APK) (push) Successful in 7m59s
Note 3127 §5 removed version tags, so an artifact's self-report is now the only
answer to "which build is this?" — and nothing exists to contradict it when it
is wrong. Three surfaces gain a dim build line: the foot of the web rail, the
login screen, and the foot of Sync on Android.
The login screen because "I can't sign in" is a bug report like any other, and
requiring an account to read a build number withholds it from exactly the people
who can't get past that page. `/api/config` is already public.
Two of the values it was going to show were wrong, which is the part worth
knowing about.
The DESKTOP reported `env!("CARGO_PKG_VERSION")` from `config_get` and from the
startup log. `cargo tauri build --config '{"version": ...}'` overrides
tauri.conf.json, not Cargo's own metadata — so both read the literal `0.2.0` in
Cargo.toml, on every build ever shipped. They now read a display version baked in
by the lane through `option_env!`, hoisted to the crate root because two readers
of one fact is how this repo keeps producing 2181-2183. Not the ordering key
either: `1.0.<minutes>` is the opaque value Tauri's updater compares and must
never be shown to a person, and `update.rs` still reads it because a comparator
is exactly what it is (rule 149).
The SERVER fell back to `__version__` when APP_VERSION was absent, so a server
run from a checkout reported `0.2.0` — a real-looking version naming no build
anybody could obtain. `__init__.py` already asserted the honest answer was
"APP_VERSION being missing, which app.py already handles"; it did not, and a
comment claiming a behaviour two files away is how that stayed true-sounding.
Now an explicit "unknown", with the packaging version left where "unknown" is
not a legal value.
Android reads the INSTALLED package's versionName rather than BuildConfig, so it
reports what is actually on the phone.
Everything renders "unknown" rather than blank when it cannot say. A blank looks
like a layout bug; a plausible default cannot be caught by anything.
build.rs gets `rerun-if-env-changed` for the baked value: cargo does not track an
`option_env!` variable on its own, and the desktop lane having no cache today is
what makes that easy to forget the day one is added.
106 lines
3.6 KiB
Vue
106 lines
3.6 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from "vue";
|
|
import { useRouter, useRoute } from "vue-router";
|
|
import { useSessionStore } from "../stores/session";
|
|
import { useConfigStore } from "../stores/config";
|
|
import BaseInput from "../components/BaseInput.vue";
|
|
import BaseButton from "../components/BaseButton.vue";
|
|
import type { ApiError } from "../api/client";
|
|
|
|
const session = useSessionStore();
|
|
const config = useConfigStore();
|
|
const router = useRouter();
|
|
const route = useRoute();
|
|
|
|
const email = ref("");
|
|
const password = ref("");
|
|
const error = ref("");
|
|
const loading = ref(false);
|
|
|
|
// Only follow an in-app absolute path from ?redirect= — reject protocol-relative
|
|
// ("//host") and backslash ("/\\host") forms a browser may treat as an off-site URL,
|
|
// so a crafted login link can't bounce the user elsewhere after they sign in.
|
|
function safeRedirect(raw: unknown): string {
|
|
if (typeof raw !== "string" || !raw.startsWith("/")) return "/";
|
|
if (raw.startsWith("//") || raw.startsWith("/\\")) return "/";
|
|
return raw;
|
|
}
|
|
|
|
async function submit() {
|
|
error.value = "";
|
|
loading.value = true;
|
|
try {
|
|
await session.login(email.value, password.value);
|
|
await router.replace(safeRedirect(route.query.redirect));
|
|
} catch (e) {
|
|
error.value = (e as ApiError).error ?? "Could not sign in.";
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<main class="flex min-h-full items-center justify-center px-4 py-12">
|
|
<div class="w-full max-w-sm">
|
|
<div class="mb-8 text-center">
|
|
<div
|
|
class="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-xl bg-brand text-lg font-black text-neutral-900"
|
|
>
|
|
TS
|
|
</div>
|
|
<h1 class="text-2xl font-bold tracking-tight">Welcome back</h1>
|
|
<p class="mt-1 text-sm text-neutral-500 dark:text-neutral-400">Sign in to your thoughts.</p>
|
|
</div>
|
|
|
|
<form class="flex flex-col gap-4" novalidate @submit.prevent="submit">
|
|
<BaseInput
|
|
id="email"
|
|
v-model="email"
|
|
label="Email"
|
|
type="email"
|
|
autocomplete="email"
|
|
placeholder="you@example.com"
|
|
required
|
|
/>
|
|
<BaseInput
|
|
id="password"
|
|
v-model="password"
|
|
label="Password"
|
|
type="password"
|
|
autocomplete="current-password"
|
|
placeholder="••••••••"
|
|
required
|
|
/>
|
|
<p
|
|
v-if="error"
|
|
role="alert"
|
|
class="rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700 dark:bg-red-950/50 dark:text-red-300"
|
|
>
|
|
{{ error }}
|
|
</p>
|
|
<BaseButton type="submit" :loading="loading">Sign in</BaseButton>
|
|
</form>
|
|
|
|
<p v-if="config.allowRegistration" class="mt-6 text-center text-sm text-neutral-500 dark:text-neutral-400">
|
|
No account?
|
|
<RouterLink to="/register" class="font-semibold text-brand-700 hover:underline dark:text-brand"
|
|
>Create one</RouterLink
|
|
>
|
|
</p>
|
|
|
|
<!-- The build, on the one screen a person can reach WITHOUT an account.
|
|
"I can't sign in" is a bug report like any other and it needs a build
|
|
number; requiring a login to read one would withhold it from exactly the
|
|
people who cannot get past this page. `/api/config` is public, so this
|
|
costs nothing that was not already public (#3181). -->
|
|
<p
|
|
class="mt-8 select-all text-center text-[11px] text-neutral-400 dark:text-neutral-500"
|
|
:title="`ThoughtSync server build ${config.version || 'unknown'}`"
|
|
>
|
|
{{ config.version || "unknown" }}
|
|
</p>
|
|
</div>
|
|
</main>
|
|
</template>
|