From d8d042349a7719fc7d48805186a1e115a40aa2c2 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 20 Aug 2026 17:10:01 +0300 Subject: [PATCH 1/2] Close gaps in off-queue DX 49/54/55. Pass -c through to dotnet run so Release stamp and process match, assert changelog order for more than one commit, and restore the last school tab from sessionStorage when opening from the menu. Co-authored-by: Cursor --- docs/design/off-queue/skip-stale-build.md | 12 +++++------ run-aspire.ps1 | 1 + run-aspire.sh | 2 +- src/HSchool.Client/src/ui/gameScreen.test.ts | 20 +++++++++++++++++++ .../BuildChangelogTests.cs | 17 ++++++++++------ 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/docs/design/off-queue/skip-stale-build.md b/docs/design/off-queue/skip-stale-build.md index 4c84bad..003ce45 100644 --- a/docs/design/off-queue/skip-stale-build.md +++ b/docs/design/off-queue/skip-stale-build.md @@ -14,18 +14,18 @@ **Стало.** Если `HSchool.AppHost.dll` и `HSchool.Server.dll` новее входов (C#, csproj, props, `global.json`), скрипт делает `dotnet run --no-build`. Иначе — `dotnet build`, потом `--no-build`. -`--rebuild` собирает всегда. Конфигурация (`-c` / `--configuration`) выбирает, какие dll сравнивать; -по умолчанию Debug, как у `dotnet run`. +`--rebuild` собирает всегда. Конфигурация (`-c` / `--configuration`) выбирает, какие dll сравнивать +и с какой конфигурацией звать `dotnet run`; по умолчанию Debug. **Почему.** Инкрементальный MSBuild всё равно оценивает Aspire SDK. `--no-build` это пропускает. Клиентский TypeScript и JSONC модов не входы: Vite и каталог читают диск (`ContentRoot`), а не `bin`. Тестовые проекты AppHost не ссылается — их правки запуск не пересобирают. -Правила «устарел ли билд» живут в `tools/apphost-uptodate.ps1` (то, что вызывает `run-aspire.ps1`) и в -`LaunchBuildStamp` (то, что проверяют тесты). Менять надо оба. +Правила «устарел ли билд» живут в трёх местах: `tools/apphost-uptodate.ps1` (Windows), +`needs_build` в `run-aspire.sh` (Ubuntu) и `LaunchBuildStamp` (то, что проверяют тесты). +Менять надо все три. ### Что не входит Голый `dotnet run --project src/HSchool.AppHost` по-прежнему собирает — так удобнее агентам с -грязным деревом. Linux-обёртки нет: точка входа Windows — `.cmd`. Протокол, HTTP и сейв не -трогаем. +грязным деревом. Протокол, HTTP и сейв не трогаем. diff --git a/run-aspire.ps1 b/run-aspire.ps1 index f12dc4b..00ceaf8 100644 --- a/run-aspire.ps1 +++ b/run-aspire.ps1 @@ -121,6 +121,7 @@ $run = @( '--project', (Join-Path $PSScriptRoot 'src\HSchool.AppHost\HSchool.AppHost.csproj') '--no-build' '--no-restore' + '-c', $configuration ) + $dotnetArgs & dotnet run @run $exitCode = $LASTEXITCODE diff --git a/run-aspire.sh b/run-aspire.sh index b79fc01..8da89c1 100644 --- a/run-aspire.sh +++ b/run-aspire.sh @@ -131,7 +131,7 @@ fi echo set +e -dotnet run --project src/HSchool.AppHost/HSchool.AppHost.csproj --no-build --no-restore "${dotnet_args[@]}" +dotnet run --project src/HSchool.AppHost/HSchool.AppHost.csproj --no-build --no-restore -c "$configuration" "${dotnet_args[@]}" exit_code=$? set -e diff --git a/src/HSchool.Client/src/ui/gameScreen.test.ts b/src/HSchool.Client/src/ui/gameScreen.test.ts index 66f2a7a..7e8161c 100644 --- a/src/HSchool.Client/src/ui/gameScreen.test.ts +++ b/src/HSchool.Client/src/ui/gameScreen.test.ts @@ -7,6 +7,7 @@ import { getLocale, setLocale } from '../i18n/locale.ts'; import { t } from '../i18n/strings.ts'; import { GameScreen } from './gameScreen.ts'; import type { School } from '../net/api.ts'; +import { patchChrome } from './viewState.ts'; vi.mock('../net/api.ts', async (importOriginal) => { const actual = await importOriginal(); @@ -272,6 +273,25 @@ describe('GameScreen view restore', () => { expect(screen.element.querySelector('.panel__body--fill')?.hasAttribute('hidden')).toBe(false); }); + it('reopens the last tab from sessionStorage when opening from the menu', () => { + history.replaceState(null, '', '/'); + patchChrome(1, { route: { tab: 'people' } }); + const screen = new GameScreen({ + onLeave: () => {}, + onSetRunning: () => {}, + onSetSpeed: () => {}, + onSkip: () => {}, + }); + document.body.append(screen.element); + screen.show(school()); + + const peopleTab = [...screen.element.querySelectorAll('.panel__tab')].find( + (button) => button.textContent === t('peopleTitle'), + ); + expect(peopleTab?.classList.contains('panel__tab--active')).toBe(true); + expect(location.search).toContain('tab=people'); + }); + it('keeps a guest on overview even when the URL asks for manage', () => { history.replaceState(null, '', '/?school=3&mode=manage'); const screen = new GameScreen({ diff --git a/tests/HSchool.Server.Tests/BuildChangelogTests.cs b/tests/HSchool.Server.Tests/BuildChangelogTests.cs index 6732403..ee83658 100644 --- a/tests/HSchool.Server.Tests/BuildChangelogTests.cs +++ b/tests/HSchool.Server.Tests/BuildChangelogTests.cs @@ -5,13 +5,15 @@ namespace HSchool.Server.Tests; public class BuildChangelogTests { private const string Head = "cccccccccccccccccccccccccccccccccccccccc"; + private const string Mid = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; private const string Mark = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; private const string Merge = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; private const string Unknown = "dddddddddddddddddddddddddddddddddddddddd"; private static readonly string Sample = string.Join('\n', Head, - $"{Head}\u001f2026-01-03T12:00:00Z\u001fAdd changelog window", + $"{Head}\u001f2026-01-04T12:00:00Z\u001fAdd changelog window", + $"{Mid}\u001f2026-01-03T12:00:00Z\u001fSkip stale AppHost build", $"{Mark}\u001f2026-01-02T12:00:00Z\u001fMark phase 54 in progress.", $"{Merge}\u001f2026-01-01T12:00:00Z\u001fMerge branch 'phase/53-weather-commute'"); @@ -22,9 +24,11 @@ public class BuildChangelogTests var visible = changelog.VisibleSince(Merge); - var commit = Assert.Single(visible); - Assert.Equal(Head, commit.Sha); - Assert.Equal("Add changelog window", commit.Subject); + Assert.Equal(2, visible.Count); + Assert.Equal(Mid, visible[0].Sha); + Assert.Equal("Skip stale AppHost build", visible[0].Subject); + Assert.Equal(Head, visible[1].Sha); + Assert.Equal("Add changelog window", visible[1].Subject); } [Fact] @@ -34,8 +38,9 @@ public class BuildChangelogTests var visible = changelog.VisibleSince(Mark); - var commit = Assert.Single(visible); - Assert.Equal("Add changelog window", commit.Subject); + Assert.Equal(2, visible.Count); + Assert.Equal("Skip stale AppHost build", visible[0].Subject); + Assert.Equal("Add changelog window", visible[1].Subject); } [Fact] From 1265f184d5f25ffdbe9e168da9db7dd61809554c Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 20 Aug 2026 17:10:50 +0300 Subject: [PATCH 2/2] Record off-queue DX 49/54/55 review journal. Co-authored-by: Cursor --- docs/phases/off-queue/reviewed.md | 26 +++++++++++++++++++++++++- docs/phases/reviewed.md | 1 + 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/phases/off-queue/reviewed.md b/docs/phases/off-queue/reviewed.md index f64be68..98d7b5a 100644 --- a/docs/phases/off-queue/reviewed.md +++ b/docs/phases/off-queue/reviewed.md @@ -1,6 +1,7 @@ # Проверенные фазы «Вне очереди» -Только портреты (60–61). Соседей по папке этот журнал не закрывает. +Разделы ниже — отдельные проходы. Чужой раздел не затирать. 48, 50–53 и 62 этот журнал +не закрывает. ## Портреты. Модели, слои промпта, LoRA и embeddings @@ -20,3 +21,26 @@ Discovery читает `models.Embedding` (офлайн — пустой спи ### Открытое Ничего, что требовало бы решения. `swarmui.json` на диске не пишет ключи embeddings — после `NormalizeAfterLoad` списки пустые, как в `Catalog()`. + +## DX. Пропуск сборки, «Что нового», состояние экрана + +- **Фазы:** 49, 54, 55 +- **Проверен на:** `d8d0423`, 2026-08-20 +- **Пути:** `tools/apphost-uptodate.ps1`, `run-aspire.cmd`, `run-aspire.ps1`, `run-aspire.sh`, `tests/HSchool.AppHost.Tests/LaunchBuildStamp.cs`, `tests/HSchool.AppHost.Tests/LaunchBuildStampTests.cs`, `src/HSchool.Server/Changelog/BuildChangelog.cs`, `src/HSchool.Server/Api/ChangelogEndpoints.cs`, `src/HSchool.Server/HSchool.Server.csproj`, `tools/embed-changelog.ps1`, `tools/embed-changelog.sh`, `tests/HSchool.Server.Tests/BuildChangelogTests.cs`, `tests/HSchool.AppHost.Tests/ChangelogApiTests.cs`, `src/HSchool.Client/src/ui/whatsNew.ts`, `src/HSchool.Client/src/ui/whatsNew.test.ts`, `src/HSchool.Client/src/ui/seenRevision.ts`, `src/HSchool.Client/src/ui/viewState.ts`, `src/HSchool.Client/src/ui/viewState.test.ts`, `src/HSchool.Client/src/ui/gameScreen.ts`, `src/HSchool.Client/src/main.ts`, `docs/protocol.md`, `docs/design/off-queue/skip-stale-build.md`, `docs/design/off-queue/whats-new.md`, `docs/design/off-queue/view-state.md` +- **Итог:** обещания 49/54/55 совпадают с кодом. Сокет не бампили (v9). Дописаны порядок нескольких коммитов в changelog и восстановление вкладки с меню из `sessionStorage`. `run-aspire` передаёт `-c` в `dotnet run`, не только в штамп. Дизайн 49 догнан до `run-aspire.sh`. `BuildChangelogTests` зелёные (4). Хостовые `ChangelogApiTests` и `LaunchBuildStampTests` в этом проходе не гонялись (штамп не менялся; HTTP не менялся; живой сервер пользователя не трогали). + +### Фаза 49 + +`apphost-uptodate.ps1` / `LaunchBuildStamp` / `needs_build` в `.sh` смотрят AppHost+Server dll против `*.cs`/`*.csproj`/`*.props` под `src/` и трёх named props; TS/JSONC и `bin`/`obj`/`node_modules` не входы. Семь тестов штампа на временных папках есть, без `AppHostFixture`. `--rebuild` собирает и затем стартует AppHost. + +### Фаза 54 + +SHA и first-parent лог вшиваются при сборке Server, не из рантайма. `GET /api/changelog` за сессией: пусто без `since` / неизвестный / текущий SHA; `Mark phase N` в списке нет, якорем остаётся. Кука `hschool.seen-rev` (год, `Path=/`, `SameSite=Lax`, не HttpOnly). Модалка после `ensureSession` на старте и после logout, не при выходе из школы на меню. + +### Фаза 55 + +Query держит школу/режим/вкладки/выбор; `sessionStorage` ключ `h-school.view.{id}` — фильтры людей, соискателей, карточку и управление. Вход/выход — `pushState`, вкладки и фильтры — `replaceState`. Гость с `mode=manage` остаётся в обзоре. F5 с `?school=` шлёт тот же `OpenSchool`. + +### Открытое + +Ничего, что фаза 54 обещала и не сделала. Баги сессии из журнала среза 8 (другое имя при живой куке, Data Protection) сюда не входят — не трогали. diff --git a/docs/phases/reviewed.md b/docs/phases/reviewed.md index 16ae638..6d6766b 100644 --- a/docs/phases/reviewed.md +++ b/docs/phases/reviewed.md @@ -34,3 +34,4 @@ | Срез 10. Этап A — страховка | `e2e7d30` | [10-craft](10-craft/reviewed.md) | | Срез 10. Этапы B–C — работник, карточка и дамп | `f4f726a` | [10-craft](10-craft/reviewed.md) | | Портреты (вне очереди) 60–61 | `5e23d1f` | [off-queue](off-queue/reviewed.md) | +| Вне очереди. DX 49/54/55 | `d8d0423` | [off-queue](off-queue/reviewed.md) |