Duplicate @keyframes names resolve last-wins across the whole cascade, so
every definition but the last was dead code that still read as live at its
call site. Two of the five duplicated names differed from the winner:
fadeIn style.css had an opacity-only variant before the one that
adds translateY, so every consumer was already sliding
research-pulse a background-colour pulse sat before the opacity/scale one
that actually runs
The other three (spin, loading-bounce, pulse) were byte-identical repeats.
Removing the losing definitions changes nothing rendered, which the computed
style snapshot confirms: the committed baseline is reproduced exactly across
all three pages and 24 variants. Deciding that a consumer wanted the plain
fade, or the background pulse, would be a visual change and belongs in its own
PR with screenshots.
This also unblocks the mechanical stylesheet split. While two definitions of a
name differed, neither could be moved: relocating either changes which one is
last, and therefore changes behaviour. A regression test now fails on any
duplicate name so the trap cannot come back.
Last step of splitting static/style.css by panel. This moves the cookbook,
deep research, memory and settings rules that can move into
static/css/cookbook-research-memory-settings.css.
After the three steps style.css still holds the shell and every rule whose
move would change the cascade, which on this branch is most of the file.
Eager ordered links, verbatim moves, no rendering change.
Second step of splitting static/style.css by panel. This moves the email,
calendar, notes and tasks rules that can move into
static/css/email-calendar-notes-tasks.css.
Same shape as the first step: eager ordered links, verbatim moves, no
rendering change. The header comment in each split file lists the load order
and the earlier file's header is updated so all of them agree.
static/style.css is 51,425 lines and holds every panel's styles, so two
people working on unrelated panels still edit the same file. This is the
first of three steps that give each panel group its own stylesheet. It moves
the document library, image gallery and image editor rules that can move
into static/css/documents-gallery-editor.css.
The new file is loaded eagerly from index.html immediately after style.css,
so the cascade is the concatenation of the two in that order, which is the
order those rules already had. Nothing is deferred and nothing renders
differently.
Rules moved verbatim. A rule stays in style.css when its selector group
covers more than one panel, when the class is shared design language used
from modules outside the panel, or when moving it would flip which of two
equally specific rules wins on an element the markup puts both classes on.
That last rule is what keeps this change small: most panel-prefixed rules on
this branch compete with a shared component rule somewhere later in the file.
Nineteen tests read static/style.css directly and go red the moment a rule
they assert on moves, which says nothing about the page. They now go through
tests/helpers/stylesheets.app_css(), which concatenates the stylesheets in
the order index.html loads them. The helper also exposes the <link> markup
for tests that build a synthetic page through Playwright.
The timeline's terminating dot used a single left offset (-17px) at both
breakpoints, but the thread's padding-left differs (22px desktop, 18px
mobile) and the step dots already carry a per-breakpoint offset. The 6px
dot therefore landed 2px right of the 2px rail on desktop and 2px left of
it on mobile, which is the visible kink under an expanded last step.
Derive each offset from the rail's centre instead: the rail sits at
left:5px and is 2px wide, so the dot's left edge belongs at 3px, giving
3px - padding-left per breakpoint.
* perf(ui): stop session loading from blocking shell
* fix(startup): open routes on their own data, retire the loader for good
Follow-up to review on #5927.
- Route openers are now classified by the data they actually read. Only
/email touches the hydrated session list (its new-chat path falls back to
the most recent session's model when no default chat is set), so every
other route opens as soon as module wiring completes instead of queueing
behind /api/sessions. This is the deferred-route half of #5926, which the
first pass left unimplemented.
- index.html's 5s fallback removes the loader node again. Leaving it in the
DOM indefinitely kept _shouldPreserveStartupComposer true forever on a
hung /api/sessions, so the composer stopped clearing on session switch.
- A missing session module settles hydration instead of leaving the sidebar
on "Loading chats…" and dropping the user's route on the floor.
- Startup sequencing moved to static/js/startupShell.js so it can be run by
tests. The source-text assertions in test_startup_shell_session_loading.py
are replaced by node-driven behavioural tests, per tests/TESTING_STANDARD.md.
- Reverted the unrequested loader a11y rework, removed the duplicated inert
writes (the module stops the wave interval through a callback), and moved
the bootstrap row's inline styles into .session-list-bootstrap.
* fix: preserve session bootstrap failure state
---------
Co-authored-by: Léo <leograndcontact@gmail.com>
* fix(chat): Expand user chat bubble edit textbox width
- Update user chat bubble width from `fit-content` to `85%` to ensure consistency with the AI chat bubble edit textbox width.
* style(chat): Refine user message bubble width logic
- Change general bubble width to `fit-content`
- Set width to 85% specifically for user messages containing a `textarea`
Align regression tests with the current Odysseus behavior after merging origin/dev into local main.
- keep phone/name-only contacts valid and cover null email without crashes
- pin explicit web-search false form submission in chat.js
- update Cookbook dependency/download completion tests for combined live + persisted output
- expose SGLang OS package repair hints from backend diagnosis
- treat MLX and MLX-community repos as servable on Apple Metal while keeping CUDA behavior unchanged
- keep desktop new-chat coverage on the shared preferred-model helper
- remove a hardcoded crop overlay portal z-index literal
- include the local agent-loop cleanup that removes the old manage_notes reminder repair shim
Verified with: docker run --rm -v /home/pewds/odysseus-cookbook-fresh:/app -w /app odysseus-cookbook-fresh-odysseus python3 -m pytest -q (4515 passed, 4 skipped).
The comparison grid hard-codes 2-4 equal columns with no phone breakpoint, so at
390px two models get ~178px columns and four get ~88px columns. Each column is a
full scrolling chat, so content is unreadably over-wrapped and clipped. On
phones (<=768px), stack the panes into a single scrollable column. Desktop is
unaffected.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>