From 871957ecbb2e29e7d9562a044a966ca61627c098 Mon Sep 17 00:00:00 2001 From: Navid Shad Date: Thu, 27 Aug 2026 18:35:32 +0300 Subject: [PATCH] feat(ui): add subturtle-ui design system, new app shell and progress screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces pilotui's chrome with an in-house component library built on Subturtle's real brand (rose #f91e5a, jade, Nunito, warm paper) rather than the VRISTO admin template's leftover blue. First milestone: the library, the app shell, and the Progress screen (/statistic). The remaining eight designed screens follow one per PR; pilotui stays installed until the last usage is gone. New ui/ package (subturtle-ui), a sibling of frontend/ and server/: - Vite lib mode, single ESM+CJS entry, real .d.ts, one prebuilt dist/style.css, vue as the only peer dependency. - Tailwind is compiled away at build time, with prefix 'st-' and preflight off. The library's CSS loads beside each consumer's own Tailwind build, so an unprefixed .rounded-lg or .bg-primary would collide and load order would decide styling app-wide. This also frees the library's config from having to agree with any consumer's, which is what lets subturtle-extension-apps adopt it unchanged. - Design tokens keep the names used in the design source, but store colors as RGB channels so opacity modifiers work and a dark theme is a pure-CSS override. :root.dark is scaffolded and empty; the theme switcher stays, so dark currently leaves these components in their light palette. - Icons are inlined from @iconify/json at build time: the design calls icons by runtime name, which Tailwind selectors cannot pre-generate, and the iconify-icon web component fetches from a CDN, which content scripts cannot rely on. - Components: StAppShell, StSidebarNav, StButton, StIconButton, StCard, StBadge, StAvatar, StEmptyState, StSkeleton, StIcon, StStatTile, StBundleCard. Nav hover states, focus-visible rings and the collapsible rail are added rather than ported; the design's React sources are styled with inline style objects and cannot express them. Frontend: - StAppShell becomes the default layout. pilotui's stays as the theming provider every un-migrated screen still reads from. - /statistic rebuilt with populated, empty, locked and loading states. The week-over-week delta and streak are derived client-side from a 14-day series; the RPC drops interior zero-days, so the series is re-expanded against real dates before use. Tiles read "—" rather than 0 when the tier lock hides the underlying numbers. - subturtle-ui/tailwind-tokens is spread into the app's Tailwind config so page markup can use the same tokens (bg-st-card, text-st-strong, rounded-st-lg). Known gap: the streak counts days a phrase was saved, not days reviewed. It is the closest signal available client-side; a real streak belongs on the server. Co-Authored-By: Claude Opus 5 --- frontend/components/bundle/GenerativeCard.vue | 118 +- frontend/components/common/StPageHeader.vue | 30 + .../material/WordGenerativeCover.vue | 32 +- .../components/widget/WeekActivityChart.vue | 41 + .../composables/useDashboardNavigatorItems.ts | 128 +- frontend/composables/useProgressSummary.ts | 118 ++ frontend/layouts/default.vue | 159 +- frontend/locales/en.json | 60 +- frontend/nuxt.config.ts | 17 +- frontend/package.json | 1 + frontend/pages/statistic.vue | 294 +++- frontend/tailwind.config.cjs | 13 +- frontend/utils/language.ts | 34 + frontend/yarn.lock | 4 + ui/.gitignore | 3 + ui/README.md | 137 ++ ui/package.json | 49 + ui/postcss.config.cjs | 6 + ui/scripts/build-icons.mjs | 108 ++ ui/src/brand/StBundleCard.vue | 101 ++ ui/src/brand/StStatTile.vue | 61 + ui/src/elements/StAvatar.vue | 46 + ui/src/elements/StBadge.vue | 46 + ui/src/elements/StButton.vue | 83 + ui/src/elements/StCard.vue | 32 + ui/src/elements/StEmptyState.vue | 57 + ui/src/elements/StIconButton.vue | 69 + ui/src/elements/StSkeleton.vue | 30 + ui/src/env.d.ts | 7 + ui/src/icon/StIcon.vue | 36 + ui/src/icon/icons.generated.ts | 34 + ui/src/index.ts | 31 + ui/src/shell/StAppShell.vue | 107 ++ ui/src/shell/StSidebarNav.vue | 110 ++ ui/src/styles/index.css | 57 + ui/src/styles/tokens.css | 196 +++ ui/src/types/index.ts | 20 + ui/tailwind-tokens.cjs | 103 ++ ui/tailwind.config.cjs | 158 ++ ui/tsconfig.build.json | 5 + ui/tsconfig.json | 20 + ui/vite.config.ts | 31 + ui/yarn.lock | 1549 +++++++++++++++++ 43 files changed, 4069 insertions(+), 272 deletions(-) create mode 100644 frontend/components/common/StPageHeader.vue create mode 100644 frontend/components/widget/WeekActivityChart.vue create mode 100644 frontend/composables/useProgressSummary.ts create mode 100644 frontend/utils/language.ts create mode 100644 ui/.gitignore create mode 100644 ui/README.md create mode 100644 ui/package.json create mode 100644 ui/postcss.config.cjs create mode 100644 ui/scripts/build-icons.mjs create mode 100644 ui/src/brand/StBundleCard.vue create mode 100644 ui/src/brand/StStatTile.vue create mode 100644 ui/src/elements/StAvatar.vue create mode 100644 ui/src/elements/StBadge.vue create mode 100644 ui/src/elements/StButton.vue create mode 100644 ui/src/elements/StCard.vue create mode 100644 ui/src/elements/StEmptyState.vue create mode 100644 ui/src/elements/StIconButton.vue create mode 100644 ui/src/elements/StSkeleton.vue create mode 100644 ui/src/env.d.ts create mode 100644 ui/src/icon/StIcon.vue create mode 100644 ui/src/icon/icons.generated.ts create mode 100644 ui/src/index.ts create mode 100644 ui/src/shell/StAppShell.vue create mode 100644 ui/src/shell/StSidebarNav.vue create mode 100644 ui/src/styles/index.css create mode 100644 ui/src/styles/tokens.css create mode 100644 ui/src/types/index.ts create mode 100644 ui/tailwind-tokens.cjs create mode 100644 ui/tailwind.config.cjs create mode 100644 ui/tsconfig.build.json create mode 100644 ui/tsconfig.json create mode 100644 ui/vite.config.ts create mode 100644 ui/yarn.lock diff --git a/frontend/components/bundle/GenerativeCard.vue b/frontend/components/bundle/GenerativeCard.vue index 35a324f8..682aace1 100644 --- a/frontend/components/bundle/GenerativeCard.vue +++ b/frontend/components/bundle/GenerativeCard.vue @@ -1,70 +1,74 @@ + + const withLanguages = phrases.find((p) => p.language_info?.source || p.translation_language); + if (withLanguages) { + langPair.value = { + source: toLanguageCode(withLanguages.language_info?.source), + target: toLanguageCode(withLanguages.language_info?.target ?? withLanguages.translation_language), + }; + } + } - + onMounted(async () => { + getWords(); + }); + diff --git a/frontend/components/common/StPageHeader.vue b/frontend/components/common/StPageHeader.vue new file mode 100644 index 00000000..a184df12 --- /dev/null +++ b/frontend/components/common/StPageHeader.vue @@ -0,0 +1,30 @@ + + + diff --git a/frontend/components/material/WordGenerativeCover.vue b/frontend/components/material/WordGenerativeCover.vue index 10318f5e..b044009b 100644 --- a/frontend/components/material/WordGenerativeCover.vue +++ b/frontend/components/material/WordGenerativeCover.vue @@ -1,6 +1,6 @@