Skip to content

Stage J6: move the Online page out of the window, with a new drawer, toolbar and states - #118

Merged
uyujkk merged 11 commits into
mainfrom
claude/ui-j6-online
Nov 10, 2026
Merged

uyujkk merged 11 commits into
mainfrom
claude/ui-j6-online

Conversation

@uyujkk

@uyujkk uyujkk commented Nov 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

Stage J6 (move the Online page out of MainWindow) and J6-b (its appearance and usage flow), done in one pass (docs/development/UI-Structure-Plan.md, J6; docs/development/UI-Fluent-Design-Plan.md, "在线(J6)").

Structure

  • 86d8112: inventory: 15 files, 5,414 lines plus BrowseSessions.cs; about 50 references from the window into the page and about 100 the other way.
  • 2f667e5: a test-only sample mode, IMM_ONLINE_FIXTURES.
    • When the variable is set, every online request is answered from synthetic samples in tests/TestData (JSON and generated placeholder images). Anything not in the samples fails, nothing goes to the network, and the log says it's in sample mode.
    • When it isn't set, the code path and the requests' content, frequency and caching are unchanged.
    • It only reads the sample folder. Unit tests cover both modes. It exists only for screenshots and UI tests.
  • 3f4ac10: the online image cache moves into ImageService (Attach, LoadCachedAsync, LoadOnlineBitmapAsync, GetCachedUriAsync, TrimCache).
  • 9ffdf7a: the download scheduler and journal move into DownloadService (Scheduler, JournalAvailable, Restore(), Save()).
  • 58f2e75: pure move into Pages/OnlinePage.xaml and Pages/Online/.
    • The page reaches the window only through IOnlineHost: 47 members in 8 commented groups. The page never references MainWindow.
    • The character catalogue and the effective online source stay in the page for now.
    • Full-window screenshots of all 30 views match the commit before the move, apart from the desktop at the window edge and two passing states.
  • fdbece5: a follow-up in UI-Structure-Plan.md to split IOnlineHost into character catalogue, online source and install flow services.

MainWindow: code 20,094 → 14,428 lines; MainWindow.xaml 1,199 → 705 lines.

Appearance and usage (before → after)

  • 34dba1b Character switch: paused on a 135 ms fade-out every time → starts at once.
  • 1c04721 Header and toolbar:
    • Refresh was cut off in Russian at 1000 wide → icon plus label; the label hides only when the status beside it would get under 120 px.
    • The header status was a fixed 460 px and lost its tooltip after a language change → it takes the space left, trims, always has a tooltip, and hides rather than show a single letter.
    • At 670×530 the search box was squeezed to nothing and buttons were clipped → search gets its own row; below 720 px of content the character rail folds into a character list in the toolbar.
    • Paging buttons: minimum width 84 → 96 px.
  • 868cf4c Details drawer (option A):
    • It had a hand-drawn gradient shadow, was opaque and took 34% of the width → system shadow on in-app acrylic, 45% wide (400–600 px).
    • Under 900 px of content it covers the results, and its close button becomes "Back to results", a new string in all three languages. The close button now has a tooltip and an accessible name.
    • The bottom has one main download button, "Select file" beside it, and the source page as a link.
  • d12e2b6 States:
    • Loading: three fake text bars → placeholders shaped like the current view.
    • Empty: a hand-built card → the shared empty-state control, with "Clear filters" when nothing matches. The four status strings it replaced are removed from all three languages.
    • A failed list load: a modal dialog → an error bar under the toolbar with "Retry", which does the same as Refresh.
  • The size and layout rules are in Core (OnlineLayoutPolicy) with unit tests.

Online requests, their frequency, caching, and download, install and tracking results are unchanged.

Timings (median of 5, two runs each; about 35 ms of each figure is the UI-automation check itself)

Action Before After
Switch character 300 / 300 ms 144 / 163 ms
Open details 69 / 64 ms 60 / 66 ms

Not done

  • These Fluent-plan Online items rebuild the list and grid, so they're left for a separate J6-c:
    • an ItemsView grid with UniformGridLayout;
    • a search box with recent suggestions and a command bar;
    • removable filter tags;
    • a searchable character list;
    • Space to add a Mod to the batch.
  • The Downloads navigation item overlap noted in the roadmap couldn't be reproduced at 640–900 wide and 400–530 high.

Note for #15: any unhandled exception in a XAML event handler ends the app with 0xc000027b. The real exception is written to startup.log as "Unhandled UI exception: …", so an affected user's log should name the cause.

Test plan

  • scripts\test-all.ps1: 1,779 Core tests, Release build 0 warnings, Core coverage 94.0% line / 88.1% branch
  • UI tests 70/70 on the same build, including 3 new Online tests on the synthetic samples and the navigation round-trip tests
  • Code-style check
  • 42 screenshots: zh/en/ru × 1000×800 / 1400×900 × light/dark, plus 670×530 and the empty and error states. No important Russian text is clipped.
  • Full package with the real mouse: 24 character switches, 24 opens and closes of details, 48 page switches and the narrow-window drawer; no errors
  • Loading placeholders: the samples load too fast to photograph them

🤖 Generated with Claude Code

uyujkk and others added 11 commits November 9, 2026 18:36
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For screenshots and UI tests of the Online page only. With the variable
set to a folder, every request of the app's HTTP client is answered from
the synthetic samples listed in its fixtures.json (longest URL prefix);
a request without a sample fails, is logged as "Online test fixture
mode (测试样例模式)", and nothing is sent to the network. The folder is
only read. Without the variable the client is the plain one it always
was, so requests, frequency and caching are unchanged
(OnlineHttpClientFactory; unit tests for both cases, the read-only
folder and a sample outside it).

tests/TestData/OnlineFixtures holds made-up samples: three characters,
twelve Mods with details, files and translations, and generated
placeholder images, written by tools/online-fixtures/make_fixtures.py.
TESTING.md documents the variable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ImageService now owns the image cache (cache\online-images): download
with retry and the size limit, decoding through the load coordinator,
local decoding, the existing-file lookup and trimming. The window
attaches the HTTP client and the log at startup instead of handing the
service a loader; the Online page, the workspace, avatars, the image
viewer and the Updates page load through it. Same requests, cache files
and limits as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DownloadService now holds the download scheduler (how many downloads run
at once) and restores and saves the task journal; it stops using the
journal once it could not be read, as before. The window keeps the
threading, logging and the warning notification. Same limits and same
journal files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Online XAML (with its two styles) moves to Pages/OnlinePage.xaml and the
15 Online partial files to Pages/Online/OnlinePage.*.cs. The page reaches the
window only through IOnlineHost (47 members in 8 commented groups); it never
references the MainWindow type. The window creates the page and calls the
members it used before through it.

No behaviour change: online requests, caching, downloads, installs and
tracking are untouched. The 30 Online screenshots match the baseline pixel
for pixel; test-all and all 60 UI tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every character switch faded the old cards out and waited 135 ms before
anything changed. The new cards already fade in when they arrive, so the
switch now starts at once. Measured with the Online samples at 1400x900
(median of 5, through UI Automation): 282 ms before, 157 ms after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before -> after:
- Refresh button: a text-only button that Russian cut off at 1000 px
  ("Обновить он") -> icon plus label; the label drops (icon, tooltip and
  name stay) only when the status beside it would keep under 120 px.
- Header status: fixed 460 px pill, its tooltip lost after a language
  change -> takes the room that is left, trims, always has the full text as
  a tooltip; hides when under 120 px instead of showing one letter.
- Toolbar at about 670x530: search box squeezed to nothing and the buttons
  clipped -> the search box gets its own row when it would drop below
  240 px; sort and filters go on the second row.
- Character rail below 720 px of content: a 152 px side rail -> folds into
  the existing character list in the toolbar.

The rules live in OnlineLayoutPolicy (Core) with unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before -> after:
- Shadow: a hand-drawn 24 px gradient strip, grey in dark mode -> the
  system ThemeShadow (Translation Z 32) on a transparent receiver.
- Surface: opaque elevated colour -> in-app acrylic with the flyout stroke;
  the sticky action area uses the layer fill.
- Width: 34 % of the window (480-760) or window - 150 -> 45 % of the page,
  400-600 px (OnlineLayoutPolicy.DetailDrawer, unit tested); below 900 px of
  content it covers the results area and its close button becomes "Back to
  results" (new string in zh-CN, en-US, ru-RU). The close button now has a
  tooltip and an automation name.
- Actions: three equal buttons (open page, select file, download) ->
  download as the one accent button with "Select file" beside it, and the
  source page as a hyperlink below.

The drawer still opens over the results without leaving the page; previous,
next and Esc work as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before -> after:
- Loading: a ring over three fake text bars -> the ring over placeholder
  cards shaped like the current view (rows in the list, tiles at the tile
  size in the grid).
- No results / waiting / unsupported source: a hand-built card with a
  third status line -> the shared EmptyState with an icon; "no matching
  Mods" offers "Clear filters" (which also clears the search). The four
  status lines it no longer shows (OnlineFeed.NotStarted, ReadyToRefresh,
  NoResults, Loading) are removed from all three languages.
- A failed list load: a modal dialog with the error -> an inline error
  InfoBar under the toolbar with the error and "Retry" (the same action as
  Refresh); a later successful load closes it. Requests, retries and
  cooldowns are unchanged.
- Paging buttons: 84 -> 96 px minimum, so "Назад"/"Далее" never clip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The list opens details and closes them with Esc, a character shows only its
Mods, a narrow window folds the rail into the toolbar and the drawer's close
button reads "Back to results", and a failed load shows an inline error with
Retry instead of a dialog. They run in sample mode (IMM_ONLINE_FIXTURES), so
nothing goes to the network.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@uyujkk
uyujkk changed the base branch from claude/ui-j8-dialogs to main November 10, 2026 03:57
@uyujkk
uyujkk merged commit 4f8da19 into main Nov 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant