From 4eb0a5bb379e6182a67dac70e1dfe3ff6e3c9193 Mon Sep 17 00:00:00 2001 From: Navid Shad Date: Thu, 27 Aug 2026 19:31:04 +0300 Subject: [PATCH 1/2] docs: freeze main until the UI migration completes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The redesign lands one screen at a time, so dev deliberately carries a half-migrated UI: screens rebuilt on subturtle-ui sit beside screens still on pilotui, and dark mode is light-only on the migrated ones. Merging dev into main would ship that inconsistency to production. Records the freeze under Branching, next to the dev -> main flow it overrides, and adds a Design system migration section covering what is deliberately in a half-finished state meanwhile — pilotui still being required, dark mode being light-only, and subturtle-ui needing ui/ present wherever the frontend installs. The standing dev -> main PR (#58) is closed for the same reason; a fresh one opens once the last screen is migrated and pilotui is removed. --- CLAUDE.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 3e99a8db..4cdc3356 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -153,8 +153,32 @@ CU-__ e.g. `CU-86ext1gpf_Make-subscription-tiers-Stripe-metadata-driven-adaptive-pricing-Council-004-rollout_Navid-Shad`. The `` is the ClickUp custom id (the `CU-…` shown on the task), the title is the task name with spaces → dashes, and the author is the assignee. - Branch off the latest `dev`; open a **PR into `dev`**. `dev` reaches `main` via the long-running `dev → main` PR — so a task only needs the one PR into `dev`. +- 🚧 **`main` is frozen for the duration of the UI migration. Do not merge `dev → main`.** The redesign lands one screen at a time (see [Design system migration](#design-system-migration)), so `dev` carries a deliberately half-migrated UI: screens already rebuilt on `subturtle-ui` sit beside screens still on pilotui, and dark mode is light-only on the migrated ones. Shipping that to production would put a visibly inconsistent app in front of users. The standing `dev → main` PR was closed for this reason; open a fresh one once the last screen is migrated and pilotui is removed. - **Footgun:** if you create the branch with `git switch -c origin/dev`, Git sets its upstream to `origin/dev`, and a plain `git push` (or a Git-client "sync") then lands the commits **straight on `dev`** instead of a new remote branch. Create it without that tracking and publish it explicitly: `git switch -c ` then `git push -u origin ` (or `--no-track` when branching off `origin/dev`). +## Design system migration + +The UI is being rebuilt on **`subturtle-ui`** (`ui/`), an in-house Vue 3 component library +carrying Subturtle's real brand, replacing **pilotui** — a generic admin theme whose blue +`#4361ee` palette was never ours. See [ui/README.md](ui/README.md). + +The redesign lands **one screen per PR** onto the long-running `new-design` branch, which is +promoted to `dev` in batches. While it is in progress: + +- **`main` is frozen** — see the note under [Branching](#branching). Nothing ships to + production until every screen is migrated. +- **pilotui stays installed** and is still required. Removing it is blocked on more than + components: `pilotui/style.css` supplies global classes the app markup uses directly + (`panel`, `btn`, `form-input`, `badge`, `animate__*`, `screen_loader`, `main-section`), + plus `pilotui/toast` and `useAppStore` from `pilotui/store`. Un-migrated screens render + inside the new shell and keep working. +- **Dark mode is light-only on migrated surfaces.** The theme switcher stays and + `:root.dark` is scaffolded but empty, so toggling dark leaves migrated screens light while + pilotui ones go dark. Deliberate; dark lands as its own milestone. +- `subturtle-ui` is a `link:../ui` dependency whose `dist/` is not committed, so the + frontend's `postinstall` builds it. Any context that installs the frontend needs `ui/` + present — the Dockerfile copies it in. + ## Commits & versioning This repo uses **semantic versioning**, and commit titles follow **Conventional Commits** so the type prefix maps to the intended `vMAJOR.MINOR.PATCH` bump. Pick the type by the change's real impact, not by habit: From 3883d91c23aa0ea6c6832aeef0bc19516bef9342 Mon Sep 17 00:00:00 2001 From: SomiVista Date: Sat, 29 Aug 2026 12:43:57 +0300 Subject: [PATCH 2/2] feat(ui): rebuild the login screen on subturtle-ui Replaces the admin-template login (skewed purple panel, five decorative PNGs, pilotui Button, dark: variants) with the redesigned screen: a sign-in column on warm paper with two ambient brand blobs, and a tilted, blurred preview of Today's board off the right edge. The script logic is unchanged. The ?redirect -> sessionStorage store, the authentication.isLogin early replace, the anonymous-token priming and triggerGoogleLoginProcess (timezone + redirect params, window.open with _self) are byte-identical; the only additions inside them are two phase.value assignments that drive the new views. Three states, from one `phase` ref: the form, a "Waiting for Google..." block with a retry link (window.open normally navigates away first, but it earns its place on a slow connection), and a "Signing you in..." overlay shown while an existing session is forwarded, so the form never flashes at someone who is in fact signed in. Two notice states -- session-ended and sign-in-failed -- render from ?notice=expired|failed. Nothing produces that param yet: login_with_token.vue keeps its toastError and the 401/412 interceptor still bounces here silently. Wiring either is a one-line change in those files that needs no change to this page. The two-column layout switches at xl, not lg. The column is 540px and the preview occupies 690px after its overhang, so anything under 1230px put the floating phrase cards on top of the heading. Below xl the column centres and keeps its 344px form width. LoginBoardPreview and InlineNotice stay app-local rather than moving into subturtle-ui: the design system defines a Toast but no inline notice, callout or alert, so the notice is our composition of its tokens and not a library primitive, and the board preview is login-only decoration. Same precedent as StPageHeader -- graduate on the second caller. subturtle-ui gains five icons (logos:google-icon, solar:arrow-right-bold, solar:clock-circle-bold, solar:danger-triangle-bold, solar:moon-sleep-bold-duotone) and four st- tokens the markup needed (ink-200, ink-700, red-600, shadow-xl). Both additive; no component changed. Co-Authored-By: Claude Opus 5 --- .../components/auth/LoginBoardPreview.vue | 108 ++++++ frontend/components/common/InlineNotice.vue | 47 +++ frontend/locales/en.json | 47 ++- frontend/pages/auth/login.vue | 364 ++++++++++++------ ui/scripts/build-icons.mjs | 7 +- ui/src/icon/icons.generated.ts | 5 + ui/tailwind-tokens.cjs | 4 + 7 files changed, 465 insertions(+), 117 deletions(-) create mode 100644 frontend/components/auth/LoginBoardPreview.vue create mode 100644 frontend/components/common/InlineNotice.vue diff --git a/frontend/components/auth/LoginBoardPreview.vue b/frontend/components/auth/LoginBoardPreview.vue new file mode 100644 index 00000000..0d306afc --- /dev/null +++ b/frontend/components/auth/LoginBoardPreview.vue @@ -0,0 +1,108 @@ + + + diff --git a/frontend/components/common/InlineNotice.vue b/frontend/components/common/InlineNotice.vue new file mode 100644 index 00000000..26a7c2f4 --- /dev/null +++ b/frontend/components/common/InlineNotice.vue @@ -0,0 +1,47 @@ + + + diff --git a/frontend/locales/en.json b/frontend/locales/en.json index d8bbcc2d..9ccd1aef 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -402,11 +402,52 @@ } }, "auth": { - "signin_with_google": "SIGN IN WITH GOOGLE", + "signin_with_google": "Sign in with Google", "login_page": "Login Page", - "signin": "Sign In", + "signin": "Sign in", "signup": "Sign Up", - "signin_subtitle": "Sign in with Google to get started" + "signin_subtitle": "Sign in with Google to get started", + "heading-back": "Heading back to where you left off", + "no-account": "Don't have an account ?", + "no-account-cta": "Just sign in and you will get a new account.", + "chrome-extension": "Chrome extension", + "waiting": { + "label": "Waiting for Google\u2026", + "hint": "A Google window should have opened. Nothing there?", + "retry": "Open it again" + }, + "returning": { + "title": "Signing you in\u2026", + "subtitle": "Taking you to your dashboard." + }, + "notice": { + "expired": "Your session ended, so we brought you here. Nothing you saved was lost.", + "failed": { + "title": "That didn't go through", + "message": "The Google window closed before you finished. Nothing was saved \u2014 try once more." + } + }, + "preview": { + "today": "Today", + "board-title": "Today's board", + "board-subtitle": "Two things today, about ten minutes. Day 9 of your streak.", + "due-today": "Due today", + "due": "Due", + "review-title": "Smart Review", + "review-body": "12 phrases from levels 1 to 3. Cloze cards start at level 3.", + "start-review": "Start review", + "time-left": "3 min left", + "session-title": "Live session", + "session-body": "Say this week's phrases out loud with Coach. Money Heist \u2014 s1e4.", + "start-session": "Start session", + "resting-title": "Resting", + "resting-body": "Levels 4 and 5 are asleep. Their next cards land on Thursday.", + "optional": "Optional practice", + "phrase-1": "Je n'en reviens pas", + "phrase-1-translation": "I can't believe it", + "phrase-2": "\u00c7a vaut le coup", + "phrase-2-translation": "It's worth it" + } }, "practice": { "nav": "Practice", diff --git a/frontend/pages/auth/login.vue b/frontend/pages/auth/login.vue index 02cbd0b2..5d52b62e 100644 --- a/frontend/pages/auth/login.vue +++ b/frontend/pages/auth/login.vue @@ -1,52 +1,127 @@ - // Only prime an anonymous token when no token is loaded yet — keeps - // anonymous-allowed routes usable from the login page without - // overwriting an existing authenticated session. - if (!authentication.getToken) { - authentication.loginAsAnonymous(); + diff --git a/ui/scripts/build-icons.mjs b/ui/scripts/build-icons.mjs index 3957701e..f53d1d29 100644 --- a/ui/scripts/build-icons.mjs +++ b/ui/scripts/build-icons.mjs @@ -18,18 +18,22 @@ import { fileURLToPath } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const root = resolve(here, '..'); -/** Every icon the shell and the Progress screen use. Keep sorted by set, then name. */ +/** Every icon the shell, the Progress screen and the Login screen use. Keep sorted by set, then name. */ const ICONS = [ + 'logos:google-icon', 'solar:add-circle-bold', 'solar:alt-arrow-left-linear', 'solar:alt-arrow-right-linear', 'solar:arrow-down-bold', + 'solar:arrow-right-bold', 'solar:arrow-right-linear', 'solar:arrow-up-bold', 'solar:bookmark-bold-duotone', 'solar:chart-2-bold-duotone', + 'solar:clock-circle-bold', 'solar:crown-bold', 'solar:crown-bold-duotone', + 'solar:danger-triangle-bold', 'solar:documents-bold-duotone', 'solar:download-minimalistic-bold', 'solar:fire-bold', @@ -39,6 +43,7 @@ const ICONS = [ 'solar:layers-minimalistic-bold-duotone', 'solar:lock-keyhole-minimalistic-bold-duotone', 'solar:microphone-3-bold-duotone', + 'solar:moon-sleep-bold-duotone', 'solar:notebook-bold-duotone', 'solar:play-bold', 'solar:rocket-2-bold-duotone', diff --git a/ui/src/icon/icons.generated.ts b/ui/src/icon/icons.generated.ts index 180d8990..8bbbfffd 100644 --- a/ui/src/icon/icons.generated.ts +++ b/ui/src/icon/icons.generated.ts @@ -7,16 +7,20 @@ export interface IconDef { } export const icons: Record = { + "logos:google-icon": { body: "", width: 256, height: 262 }, "solar:add-circle-bold": { body: "", width: 24, height: 24 }, "solar:alt-arrow-left-linear": { body: "", width: 24, height: 24 }, "solar:alt-arrow-right-linear": { body: "", width: 24, height: 24 }, "solar:arrow-down-bold": { body: "", width: 24, height: 24 }, + "solar:arrow-right-bold": { body: "", width: 24, height: 24 }, "solar:arrow-right-linear": { body: "", width: 24, height: 24 }, "solar:arrow-up-bold": { body: "", width: 24, height: 24 }, "solar:bookmark-bold-duotone": { body: "", width: 24, height: 24 }, "solar:chart-2-bold-duotone": { body: "", width: 24, height: 24 }, + "solar:clock-circle-bold": { body: "", width: 24, height: 24 }, "solar:crown-bold": { body: "", width: 24, height: 24 }, "solar:crown-bold-duotone": { body: "", width: 24, height: 24 }, + "solar:danger-triangle-bold": { body: "", width: 24, height: 24 }, "solar:documents-bold-duotone": { body: "", width: 24, height: 24 }, "solar:download-minimalistic-bold": { body: "", width: 24, height: 24 }, "solar:fire-bold": { body: "", width: 24, height: 24 }, @@ -26,6 +30,7 @@ export const icons: Record = { "solar:layers-minimalistic-bold-duotone": { body: "", width: 24, height: 24 }, "solar:lock-keyhole-minimalistic-bold-duotone": { body: "", width: 24, height: 24 }, "solar:microphone-3-bold-duotone": { body: "", width: 24, height: 24 }, + "solar:moon-sleep-bold-duotone": { body: "", width: 24, height: 24 }, "solar:notebook-bold-duotone": { body: "", width: 24, height: 24 }, "solar:play-bold": { body: "", width: 24, height: 24 }, "solar:rocket-2-bold-duotone": { body: "", width: 24, height: 24 }, diff --git a/ui/tailwind-tokens.cjs b/ui/tailwind-tokens.cjs index 20407589..d4946de6 100644 --- a/ui/tailwind-tokens.cjs +++ b/ui/tailwind-tokens.cjs @@ -55,10 +55,13 @@ module.exports = { 'jade-600': c('--jade-600'), 'jade-700': c('--jade-700'), 'amber-600': c('--amber-600'), + 'red-600': c('--red-600'), 'sky-600': c('--sky-600'), 'ink-100': c('--ink-100'), 'ink-150': c('--ink-150'), + 'ink-200': c('--ink-200'), 'ink-300': c('--ink-300'), + 'ink-700': c('--ink-700'), 'ink-800': c('--ink-800'), 'ink-950': c('--ink-950'), }, @@ -94,6 +97,7 @@ module.exports = { 'st-sm': 'var(--shadow-sm)', 'st-md': 'var(--shadow-md)', 'st-lg': 'var(--shadow-lg)', + 'st-xl': 'var(--shadow-xl)', 'st-primary': 'var(--shadow-primary)', }, maxWidth: {