T26: Redesign (FIT light / neutral dark themes, sign-in, overview, icons) #25

Merged
dzenda merged 18 commits from t26-redesign into t25-posthog-security 2026-11-14 17:49:41 +01:00
Owner

T26 Redesign

New visual identity with two themes and a System/Light/Dark switch. Stacked on #24 (T25).

Themes

  • Light: FIT ČVUT blue #0068B9 primary, gold #F9B001 accent and marker.
  • Dark: Claude-like neutrals (#191919 page, #1F1F1F surfaces, #303030 borders, #EFEFEF text and buttons). Links stay neutral and underlined.
  • Semantic colour tokens (src/styles.css) used on every screen and on the consent page. No dark: variants or raw palette colours in components.
  • The theme cookie is applied during SSR and "system" is resolved by prefers-color-scheme, so there is no flash. Theme state is shared between all Preferences controls (src/lib/theme-store.ts).

Screens

  • Sign-in (signed-out / and /login): split layout. Sign-in on the left, "What FIT-OS gives you" plus the source-code link on the right. A slowly rotating orbit of the real service logos (Sirius, Marast, Klasifikace, Courses, GitLab) pauses on hover and stays still under reduced motion. No scrolling on desktop: verified at 1024×700, 1280×720, 1366×768, 1440×800 and 1920×1080, in both themes, in cs.
  • Shell: one-row sticky header with the mono FIT-OS▌ wordmark, icon navigation, and an avatar menu holding Preferences, Account and Sign out. On mobile the nav becomes a bottom tab bar.
  • Overview (new): a "Next up" list grouped by day with the next class emphasised, and a "Due soon" list with urgency chips (≤2 days red, ≤7 gold, later neutral) and source icons. A summary line gives the real totals. A status strip covers FIT, GitLab and AI assistants.
  • Calendar: a single surface with 7 equal columns. Classes and deadlines are merged in time order, with course names and rooms, a type legend and a today highlight. Mobile shows a compact agenda.
  • Connections: real logo tiles, quiet check icons for working services, an admin note under services that are off, quiet destructive actions, an empty-state call to action, and a "How your data is handled" aside.
  • AI assistants: tabs with the real ChatGPT/Claude/Claude Code/Cursor/VS Code logos, a primary Copy button and numbered steps.
  • Account: Profile and Preferences side by side. The danger zone is made of collapsed rows whose confirmation form opens only when expanded.
  • Icons come from lucide-react.

Logos

FIT service marks are vendored in public/services/ and their ownership is noted in the README there. They are not covered by the MIT licence. Assistant marks come from @lobehub/icons (MIT), simple-icons (CC0) and devicon (MIT), inlined in src/components/BrandIcons.tsx.

Design review loop

Four rounds of screenshots (light/dark, desktop/mobile) were graded by a Claude Opus subagent and by ChatGPT Astra, with feedback from both applied each round.

Round Opus Astra
0 (before) 6 6
1 6 6.5
2 7 7.5
3 7 7.5

Astra code review

Fixed:

  • [P2] Two Preferences instances (avatar menu and account page) kept separate theme state and could drift. Moved to a shared useSyncExternalStore store, with a unit test and a browser smoke test.
  • [P2] The overview dropped fit.deadlines warnings, so a failed or truncated source looked like "nothing due". Added dueSoonPartial: the overview shows a notice and drops the deadline count from the summary.

Dismissed: none.

Follow-up (design feedback)

  • Per-service switches for AI assistants: Usermap, Sirius, Grades, Marast and Courses each get a role="switch" toggle on Connections. Each row expands to show what FIT-OS reads and its scopes, marked granted or not.
    • Stored in a new service_settings table (migration 0002). A row means "off", and rows are deleted with the user (cascade).
    • Enforcement is MCP-only (the website is unaffected). registry.call rejects calls to a switched-off service with a new service_disabled error code. fit.today/fit.deadlines skip that source and add a warning. Course enrollment (Sirius) respects the Sirius switch. fit.status lists disabledServices, and the fit://help text tells assistants how to handle service_disabled.
    • setServiceEnabledFn only accepts same-origin requests from a signed-in session. Each switch allows one in-flight save at a time.
  • Courses is now a service row under GitLab. The FIT ČVUT and Usermap logos are used, and the side panel is gone. The MCP section is now "Add to your assistant of choice".
  • Sign-in: a "What is FIT-OS?" panel without list icons, the cursor wordmark as the title, and "?" plus Usermap tiles in the orbit. On mobile, the orbit peeks in from the top-right corner.
  • The overview status strip is removed (fewer upstream calls). The assistants page has a compact URL copy field, and "After adding" is folded into the setup card. The calendar's iCal card became a small Subscribe popover button next to the week navigation.

Astra review (follow-up):

  • Fixed [P1]: fit.courses.list/search code=all reached Sirius through the enrollment lookup while Sirius was switched off. Now covered by a test.
  • Fixed [P2]: fast toggling could race. The switch is now disabled while its save is in flight.

Follow-up 2 (calendar and polish)

  • Calendar: weekday columns only; a weekend day appears only if it has classes or shown parallels. The freed space holds a sticky, scrollable Upcoming list of tests, exams and Marast quizzes for the next 60 days, with urgency chips. Weekend deadlines appear there.
  • Sirius-style workflow: clicking a class expands its details (sequence number, course, room, seats taken/capacity, parallel, teachers). Show all parallels overlays every other lecture, lab and tutorial of the course that week as dashed entries, with free or full seats. This uses the new MCP function fit.sirius.courseEvents (/courses/{code}/events, verified live with the personal Sirius scope). It returns only class data, never other students, and respects the Sirius switch.
  • The calendar subscription is a Subscribe popover. On mobile, the theme and language switches sit in the sign-in footer, the sign-in area is 80svh, and language follows the device until chosen. No horizontal overflow at 320/375/390px (overview grid columns, account theme labels). Czech months are no longer capitalised. The assistants URL sits beside the title, and Connections uses the full width.

Astra review (follow-up 2): all fixed. Superseded parallels responses are ignored (request id). Weekend parallels get a column. The event button keeps type, room and cancelled/moved status in its accessible name, and the details repeat that status.

Follow-up 3 (time grid, agent prompt)

  • Calendar (desktop): a time grid. Hours run down the side, classes are positioned and sized by time, and overlapping classes (your own plus parallels) sit side by side via layoutLanes. Deadlines get a strip under the day headers, and today has a now-line. Details open in an overlay popover. Mobile keeps the agenda list.
  • Assistants: Copy setup prompt for an agent (agentSetupPrompt, built from the same snippets as the tabs) has a manual-copy fallback, and every MCP URL mention in the steps is a copyable chip.

Astra review (follow-up 3): all fixed. Intervals are day-local and DST-safe, clipped at midnight (dayInterval, tested). Overlap packing uses the drawn extent of short events. Blocks and deadline lists carry the day for screen readers. A clipboard failure shows the prompt to copy by hand.

Checks

bun run lint && bun run typecheck && bun run test: 302 tests pass. Smoke-tested with bun run dev and a signed-in session (overview, theme sync, no-scroll sign-in).

🤖 Generated with Claude Code

## T26 Redesign New visual identity with two themes and a System/Light/Dark switch. Stacked on #24 (T25). ### Themes - **Light:** FIT ČVUT blue `#0068B9` primary, gold `#F9B001` accent and marker. - **Dark:** Claude-like neutrals (`#191919` page, `#1F1F1F` surfaces, `#303030` borders, `#EFEFEF` text and buttons). Links stay neutral and underlined. - Semantic colour tokens (`src/styles.css`) used on every screen and on the consent page. No `dark:` variants or raw palette colours in components. - The theme cookie is applied during SSR and "system" is resolved by `prefers-color-scheme`, so there is no flash. Theme state is shared between all Preferences controls (`src/lib/theme-store.ts`). ### Screens - **Sign-in (signed-out `/` and `/login`):** split layout. Sign-in on the left, "What FIT-OS gives you" plus the source-code link on the right. A slowly rotating orbit of the real service logos (Sirius, Marast, Klasifikace, Courses, GitLab) pauses on hover and stays still under reduced motion. No scrolling on desktop: verified at 1024×700, 1280×720, 1366×768, 1440×800 and 1920×1080, in both themes, in cs. - **Shell:** one-row sticky header with the mono `FIT-OS▌` wordmark, icon navigation, and an avatar menu holding Preferences, Account and Sign out. On mobile the nav becomes a bottom tab bar. - **Overview (new):** a "Next up" list grouped by day with the next class emphasised, and a "Due soon" list with urgency chips (≤2 days red, ≤7 gold, later neutral) and source icons. A summary line gives the real totals. A status strip covers FIT, GitLab and AI assistants. - **Calendar:** a single surface with 7 equal columns. Classes and deadlines are merged in time order, with course names and rooms, a type legend and a today highlight. Mobile shows a compact agenda. - **Connections:** real logo tiles, quiet check icons for working services, an admin note under services that are off, quiet destructive actions, an empty-state call to action, and a "How your data is handled" aside. - **AI assistants:** tabs with the real ChatGPT/Claude/Claude Code/Cursor/VS Code logos, a primary Copy button and numbered steps. - **Account:** Profile and Preferences side by side. The danger zone is made of collapsed rows whose confirmation form opens only when expanded. - Icons come from `lucide-react`. ### Logos FIT service marks are vendored in `public/services/` and their ownership is noted in the README there. They are not covered by the MIT licence. Assistant marks come from `@lobehub/icons` (MIT), simple-icons (CC0) and devicon (MIT), inlined in `src/components/BrandIcons.tsx`. ### Design review loop Four rounds of screenshots (light/dark, desktop/mobile) were graded by a Claude Opus subagent and by ChatGPT Astra, with feedback from both applied each round. | Round | Opus | Astra | |---|---|---| | 0 (before) | 6 | 6 | | 1 | 6 | 6.5 | | 2 | 7 | 7.5 | | 3 | 7 | 7.5 | ### Astra code review Fixed: - [P2] Two Preferences instances (avatar menu and account page) kept separate theme state and could drift. Moved to a shared `useSyncExternalStore` store, with a unit test and a browser smoke test. - [P2] The overview dropped `fit.deadlines` warnings, so a failed or truncated source looked like "nothing due". Added `dueSoonPartial`: the overview shows a notice and drops the deadline count from the summary. Dismissed: none. ### Follow-up (design feedback) - **Per-service switches for AI assistants:** Usermap, Sirius, Grades, Marast and Courses each get a `role="switch"` toggle on Connections. Each row expands to show what FIT-OS reads and its scopes, marked granted or not. - Stored in a new `service_settings` table (migration `0002`). A row means "off", and rows are deleted with the user (cascade). - Enforcement is MCP-only (the website is unaffected). `registry.call` rejects calls to a switched-off service with a new `service_disabled` error code. `fit.today`/`fit.deadlines` skip that source and add a warning. Course enrollment (Sirius) respects the Sirius switch. `fit.status` lists `disabledServices`, and the `fit://help` text tells assistants how to handle `service_disabled`. - `setServiceEnabledFn` only accepts same-origin requests from a signed-in session. Each switch allows one in-flight save at a time. - Courses is now a service row under GitLab. The FIT ČVUT and Usermap logos are used, and the side panel is gone. The MCP section is now "Add to your assistant of choice". - **Sign-in:** a "What is FIT-OS?" panel without list icons, the cursor wordmark as the title, and "?" plus Usermap tiles in the orbit. On mobile, the orbit peeks in from the top-right corner. - The overview status strip is removed (fewer upstream calls). The assistants page has a compact URL copy field, and "After adding" is folded into the setup card. The calendar's iCal card became a small **Subscribe** popover button next to the week navigation. Astra review (follow-up): - Fixed [P1]: `fit.courses.list`/`search code=all` reached Sirius through the enrollment lookup while Sirius was switched off. Now covered by a test. - Fixed [P2]: fast toggling could race. The switch is now disabled while its save is in flight. ### Follow-up 2 (calendar and polish) - **Calendar:** weekday columns only; a weekend day appears only if it has classes or shown parallels. The freed space holds a sticky, scrollable **Upcoming** list of tests, exams and Marast quizzes for the next 60 days, with urgency chips. Weekend deadlines appear there. - **Sirius-style workflow:** clicking a class expands its details (sequence number, course, room, seats taken/capacity, parallel, teachers). **Show all parallels** overlays every other lecture, lab and tutorial of the course that week as dashed entries, with free or full seats. This uses the new MCP function `fit.sirius.courseEvents` (`/courses/{code}/events`, verified live with the personal Sirius scope). It returns only class data, never other students, and respects the Sirius switch. - The calendar subscription is a **Subscribe** popover. On mobile, the theme and language switches sit in the sign-in footer, the sign-in area is 80svh, and language follows the device until chosen. No horizontal overflow at 320/375/390px (overview grid columns, account theme labels). Czech months are no longer capitalised. The assistants URL sits beside the title, and Connections uses the full width. Astra review (follow-up 2): all fixed. Superseded parallels responses are ignored (request id). Weekend parallels get a column. The event button keeps type, room and cancelled/moved status in its accessible name, and the details repeat that status. ### Follow-up 3 (time grid, agent prompt) - **Calendar (desktop):** a time grid. Hours run down the side, classes are positioned and sized by time, and overlapping classes (your own plus parallels) sit side by side via `layoutLanes`. Deadlines get a strip under the day headers, and today has a now-line. Details open in an overlay popover. Mobile keeps the agenda list. - **Assistants:** **Copy setup prompt for an agent** (`agentSetupPrompt`, built from the same snippets as the tabs) has a manual-copy fallback, and every MCP URL mention in the steps is a copyable chip. Astra review (follow-up 3): all fixed. Intervals are day-local and DST-safe, clipped at midnight (`dayInterval`, tested). Overlap packing uses the drawn extent of short events. Blocks and deadline lists carry the day for screen readers. A clipboard failure shows the prompt to copy by hand. ### Checks `bun run lint && bun run typecheck && bun run test`: 302 tests pass. Smoke-tested with `bun run dev` and a signed-in session (overview, theme sync, no-scroll sign-in). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Logos: FIT service marks vendored in public/services (see README there); assistant marks
from @lobehub/icons (MIT), simple-icons (CC0) and devicon (MIT).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- service_settings table (migration 0002); MCP calls to a switched-off service fail with
  service_disabled, aggregates skip it with a warning, fit.status lists it
- setServiceEnabledFn: same-origin + session only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- fit.sirius.courseEvents: every parallel of a course (room, teachers, capacity/occupied),
  shared paging helper with fit.sirius.events; respects the Sirius MCP switch
- getCourseWeek server fn for the calendar overlay; upcoming deadlines (60 days) beside the week

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- layoutLanes/pragueMinutes in src/lib/calendar.ts (tested)
- shared EventDetails popover; agenda list stays on mobile
- agentSetupPrompt in src/mcp/setup.ts (tested)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Referrer-Policy: same-origin (no-referrer made browsers send Origin: null on our form POSTs,
  so every management form was rejected with 403); shared isSameOriginRequest accepts
  Origin: null only with Sec-Fetch-Site: same-origin
- No COOP on OAuth/consent/login routes (popup-based web MCP clients keep window.opener)
- fit.sirius.icalUrl is websiteOnly: excluded from the MCP registry (URL embeds a credential)
- docs: telemetry wording (operator-enabled, consent advice), CSRF/COOP/credential notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dzenda merged commit 01f60c66a1 into t25-posthog-security 2026-11-14 17:49:41 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
dzenda/os!25
No description provided.