Files
inkwell/frontend/src/views/LoginView.vue
T
Bryan Van Deusen f992439588
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
version: every surface can say which build it is, and two of them were lying
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.
2026-08-29 23:07:29 -04:00

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>