feat(admin): show deployed app version
This commit is contained in:
@@ -919,6 +919,60 @@ public sealed class AuthAndSystemControllerTests
|
||||
Assert.Equal("person@example.com", result.GoogleLink.Email);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Me_result_includes_configured_build_metadata()
|
||||
{
|
||||
var user = new ApplicationUser { Id = "admin-1", Email = "admin@example.com", UserName = "admin" };
|
||||
var users = CreateUserManager();
|
||||
users.Setup(x => x.GetUserAsync(It.IsAny<System.Security.Claims.ClaimsPrincipal>())).ReturnsAsync(user);
|
||||
users.Setup(x => x.GetRolesAsync(user)).ReturnsAsync(new List<string> { "Admin" });
|
||||
var cfg = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["App:Version"] = "2.4.1",
|
||||
["App:CommitSha"] = "abc1234",
|
||||
})
|
||||
.Build();
|
||||
var controller = new AuthController(cfg, users.Object, Mock.Of<ITokenService>(), Mock.Of<IAppEmailSender>(), Mock.Of<IGoogleTokenValidator>(), Mock.Of<IMicrosoftTokenValidator>(), NullLogger<AuthController>.Instance, Mock.Of<ITwoFactorPendingTokenService>(), TestHostFactory.CreateInMemoryDb())
|
||||
{
|
||||
ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() }
|
||||
};
|
||||
|
||||
var response = await controller.Me(CancellationToken.None);
|
||||
|
||||
var ok = Assert.IsType<OkObjectResult>(response);
|
||||
var result = Assert.IsType<AuthController.MeResult>(ok.Value);
|
||||
Assert.Equal("2.4.1", result.AppVersion);
|
||||
Assert.Equal("abc1234", result.AppCommitSha);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Me_result_limits_build_metadata_to_admins()
|
||||
{
|
||||
var user = new ApplicationUser { Id = "user-1", Email = "person@example.com", UserName = "person" };
|
||||
var users = CreateUserManager();
|
||||
users.Setup(x => x.GetUserAsync(It.IsAny<System.Security.Claims.ClaimsPrincipal>())).ReturnsAsync(user);
|
||||
users.Setup(x => x.GetRolesAsync(user)).ReturnsAsync(new List<string>());
|
||||
var cfg = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["App:Version"] = "2.4.1",
|
||||
["App:CommitSha"] = "abc1234",
|
||||
})
|
||||
.Build();
|
||||
var controller = new AuthController(cfg, users.Object, Mock.Of<ITokenService>(), Mock.Of<IAppEmailSender>(), Mock.Of<IGoogleTokenValidator>(), Mock.Of<IMicrosoftTokenValidator>(), NullLogger<AuthController>.Instance, Mock.Of<ITwoFactorPendingTokenService>(), TestHostFactory.CreateInMemoryDb())
|
||||
{
|
||||
ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext() }
|
||||
};
|
||||
|
||||
var response = await controller.Me(CancellationToken.None);
|
||||
|
||||
var ok = Assert.IsType<OkObjectResult>(response);
|
||||
var result = Assert.IsType<AuthController.MeResult>(ok.Value);
|
||||
Assert.Equal("unknown", result.AppVersion);
|
||||
Assert.Null(result.AppCommitSha);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Admin_system_email_settings_falls_back_when_override_store_is_unavailable()
|
||||
{
|
||||
|
||||
@@ -92,7 +92,11 @@ public sealed class AuthController : ControllerBase
|
||||
string Plan,
|
||||
AccountEntitlements Entitlements,
|
||||
GoogleLinkDto? GoogleLink,
|
||||
MicrosoftLinkDto? MicrosoftLink);
|
||||
MicrosoftLinkDto? MicrosoftLink)
|
||||
{
|
||||
public string AppVersion { get; init; } = "unknown";
|
||||
public string? AppCommitSha { get; init; }
|
||||
}
|
||||
public sealed record PendingEmailChangeResult(string? PendingEmail, DateTimeOffset? RequestedAtUtc);
|
||||
private const int MaxAvatarBytes = 1_000_000;
|
||||
private static readonly HashSet<string> AllowedAvatarExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||
@@ -468,7 +472,8 @@ public sealed class AuthController : ControllerBase
|
||||
if (user is not null)
|
||||
{
|
||||
var roles = await _users.GetRolesAsync(user);
|
||||
return Ok(ToMeResult(user, roles));
|
||||
var isAdmin = roles.Contains("Admin", StringComparer.OrdinalIgnoreCase);
|
||||
return Ok(WithBuildMetadata(ToMeResult(user, roles), isAdmin));
|
||||
}
|
||||
|
||||
var email = User.FindFirstValue(ClaimTypes.Email) ?? User.FindFirstValue("email");
|
||||
@@ -480,7 +485,7 @@ public sealed class AuthController : ControllerBase
|
||||
? "microsoft"
|
||||
: "external";
|
||||
|
||||
return Ok(new MeResult(
|
||||
return Ok(WithBuildMetadata(new MeResult(
|
||||
Provider: provider,
|
||||
Id: sub,
|
||||
Email: email,
|
||||
@@ -495,7 +500,7 @@ public sealed class AuthController : ControllerBase
|
||||
Plan: "free",
|
||||
Entitlements: AccountPlans.ForRoles(Array.Empty<string>()),
|
||||
GoogleLink: provider == "google" ? new GoogleLinkDto(false, email, null) : null,
|
||||
MicrosoftLink: provider == "microsoft" ? new MicrosoftLinkDto(false, email, null) : null));
|
||||
MicrosoftLink: provider == "microsoft" ? new MicrosoftLinkDto(false, email, null) : null), false));
|
||||
}
|
||||
|
||||
[HttpPut("profile")]
|
||||
@@ -1195,4 +1200,15 @@ public sealed class AuthController : ControllerBase
|
||||
Email: user.MicrosoftEmail,
|
||||
LinkedAt: user.MicrosoftLinkedAt));
|
||||
}
|
||||
|
||||
private MeResult WithBuildMetadata(MeResult result, bool include)
|
||||
{
|
||||
if (!include) return result;
|
||||
|
||||
return result with
|
||||
{
|
||||
AppVersion = BuildMetadata.ResolveVersion(_cfg),
|
||||
AppCommitSha = BuildMetadata.Normalize(_cfg["App:CommitSha"]),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,3 +197,4 @@ Output was reduced to filenames and commit counts. The token artifact appears un
|
||||
| V-163 | Focused notification-popover/AppShell/Operations Jest; production frontend build; direct-navigation review | `job-tracker-ui` | Verify the header bell opens notification UI instead of routing to Reminders/Operations, while preserving global activity access | PASS — 3 suites and 6/6 tests; optimized TypeScript build passes. Popover fetch, count exposure, read, dismiss, notification-owned navigation and empty state are covered; Operations remains reachable through explicit “View all activity” | JSDOM/mocked API only; browser positioning/focus/theme and production remain | Repository increment verified |
|
||||
| V-164 | Career/Profile focused Jest; CV extraction/diff backend tests; AI-sidecar pytest; production frontend build; ingestion execution-path review | Repository root / `job-tracker-ui` / `tools/summarizer` | Reproduce and fix career-field resets while assessing the proposed Ollama accuracy pipeline | PASS — Career 17/17 including active-poll preservation; backend 8/8; sidecar 22/22; build passes. Polling now fetches run status only. Existing pipeline is confirmed as local parser/OCR → Ollama-first normalize/classify → deterministic C# validation/diff/review | Synthetic/JSDOM/fake model only; no real private CV, live Ollama model comparison, provider or production call. Repository `.venv` lacked pytest; global Python passed | Repository correction verified; model benchmark remains external/runtime work |
|
||||
| V-165 | CV renderer/resolver/template tests; Builder helper/editor/list Jest; optimized frontend build; real headless Chromium DOM/PDF probe; diff check | Repository root / `job-tracker-ui` | Verify professional multi-page CV rendering, physical preview metrics, custom-section ordering and stored-output safety under pathological content | PASS — backend 25/25; frontend 21/21; build passes. A 14-role/75-skill fixture with oversized name/email/URL produced zero horizontal offenders and a 9-page 173,196-byte PDF with extractable final-page text. Custom sections share persisted order; partial pages use ceiling count; stale preview/save/export races are gated | Synthetic data and local Chromium only; authenticated 375/768/1440 app journey, real private CV, production browser binary and DOCX remain unverified | Renderer/PDF repository scope verified; application-browser and production gates remain |
|
||||
| V-166 | Focused/full backend and frontend tests; optimized frontend build/TypeScript; diff review | Repository root / `job-tracker-ui` | Verify an administrator can identify the deployed application version without exposing build metadata in the normal-user UI/API bootstrap | PASS — focused Auth/System 36/36 and AppShell 2/2; backend 640/640; frontend 53 suites/221 tests; production build passes. Configured version/commit reaches admin `/api/auth/me`, normal users receive no configured metadata, and the responsive header badge is absent unless the admin-owned prop is supplied | Local/JSDOM evidence only; remote CI and deployed version comparison remain. Jest retains the documented force-exit/open-handle notice | Priority repository increment verified; ship proof remains |
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
Updated: 2026-08-15
|
||||
|
||||
- **Overall programme status:** Active. Seven packages are locally verified; twenty-two packages through UX-003 are implemented with automated/runtime evidence but blocked from applicable live/provider/production gates; CAREER-002 is now in progress. Gitea run 609 passes the prior complete pull-request CI; DEP-001 awaits approved merge-to-main and production verification.
|
||||
- **Current work package:** `CAREER-002` — professional CV Builder and robust rendering (`IN PROGRESS`). Renderer/editor/custom-order work and pathological Chromium/PDF proof pass; authenticated multi-width/theme application-browser regression is next.
|
||||
- **Overall programme status:** Active. Seven packages are locally verified; twenty-two are implemented with verification incomplete; JOBS-002 is in progress. The prioritized admin-only version indicator is implemented and focused-tested on the release branch; the current branch still requires full/remote gates and production verification.
|
||||
- **Current work package:** `JOBS-002` — applications table and dedicated workspace (`IN PROGRESS`). The canonical page/table/sidebar integration exists; remaining parity, dirty-edit, tenant-authorization and browser regression work is tracked in the immediate queue.
|
||||
- **Completed work packages:** None are `DONE`; all repository security/AI packages still have applicable browser, provider and/or production gates.
|
||||
- **Locally verified work:** SEC-001, SEC-002, SEC-003, SEC-005A, CORE-001, PROD-002 and DEP-001 (`VERIFIED LOCALLY`).
|
||||
- **Implemented, verification incomplete:** SEC-004, SEC-005B, SEC-008, CORE-002, BG-001, OPS-001A/B/C, POL-001/002, AI-001/002/003/004, UX-001/002/003, QA-001, CAREER-001/002, MAIL-001 and JOBS-001 (`IMPLEMENTED — NOT VERIFIED`). UX-003 safe local/browser scope is implemented; production/native-device gates remain.
|
||||
- **Production-verified work:** None.
|
||||
- **Blocked work:** SEC-006 parser upgrades remain outside the scoped frontend advisory permission; PROD-001/003/004 and REL-001 require documented production access and unfinished dependencies. Real provider, SMTP/MariaDB and production environments are unavailable; DEP-001 awaits approved merge/live verification. The in-app browser is available for local UI checks.
|
||||
- **Deferred work:** None. Conditional multi-replica coordination, model deletion, realtime operation delivery and unrelated production changes remain outside current packages.
|
||||
- **Next five work packages:** finish CAREER-002 application-browser regression; finish JOBS-002 browser regression; PRODUCT-001 homepage/Pro claims; VER-001 action matrix; production-blocked SEC-006/007 when package-index permission is available.
|
||||
- **Status counts:** 7 `VERIFIED LOCALLY`; 22 `IMPLEMENTED — NOT VERIFIED`; 1 `IN PROGRESS`; 5 `NOT STARTED`; 5 `BLOCKED`; 0 `DONE`; 0 `DEFERRED`.
|
||||
- **Test status:** backend baseline 631/631 plus admin safety 4/4, workspace 9/9, CV extraction/diff 8/8 and CV renderer/templates 25/25; frontend baseline 51/51 suites and 207/207 plus theme/confirm/admin 11/11, JOBS-002 8/8, notifications 6/6, Career 17/17 and CV Builder 21/21; AI sidecar 22/22; Playwright 6/6; pathological Chromium/PDF 9 pages with zero horizontal offenders; npm audit 0 vulnerabilities; production build passes. Historical JT-019 and Jest force-exit/open-handle behavior remain recorded.
|
||||
- **Immediate order:** admin version indicator full gate/ship proof; Career lossless manual persistence; CV contact contrast; JOBS-002 parity/regression; cross-app contrast/accessibility; PRODUCT-001; VER-001. External-only work remains skipped, not allowed to stall this queue.
|
||||
- **Status counts:** 7 `VERIFIED LOCALLY`; 22 `IMPLEMENTED — NOT VERIFIED`; 1 `IN PROGRESS`; 4 `NOT STARTED`; 5 `BLOCKED`; 0 `DONE`; 0 `DEFERRED`.
|
||||
- **Test status:** backend 640/640; frontend 53/53 suites and 221/221 tests; optimized production build/TypeScript pass. The version indicator also has focused Auth/System 36/36 and AppShell 2/2 evidence. Prior AI sidecar 22/22, Playwright 6/6, pathological nine-page Chromium/PDF proof and npm audit 0 evidence remain current. Historical JT-019 and Jest force-exit/open-handle behavior remain recorded.
|
||||
- **Deployment status:** Gitea pull-request run 609 passed the complete CI job in 4m20s. Deploy was intentionally skipped because the workflow deploys only a `push` to `main`; live remains unchanged. No merge/deployment was performed directly, no production migrations were run and the AI operation worker remains disabled by default.
|
||||
- **Production status:** Unchanged and unverified. No provider/model call, model pull, external request or paid API occurred.
|
||||
- **Known regressions:** None found by automated/local browser checks. Jest still needs `--forceExit` and reports its existing open-handle notice. Email-provider/send tests are fake/local only; real delivery is not claimed. Current MAIL browser evidence is 1280×720 only because the browser surface could not resize or perform native Tab traversal. Interrupted attempts are aged after 15 minutes and notified without retry; the five-minute scan is unmeasured on a large ledger. Direct clean EF-only SQLite migration still hits the pre-existing historical blank-chain defect before later migrations; normal startup owns reconciliation. Cross-feature monthly AI usage accounting remains a rollout gap.
|
||||
|
||||
@@ -16,7 +16,7 @@ Allowed statuses are `NOT STARTED`, `IN PROGRESS`, `IMPLEMENTED — NOT VERIFIED
|
||||
|
||||
`DONE` requires every applicable acceptance criterion, focused and regression tests, browser/accessibility/theme/mobile checks, tenant and entitlement checks, documentation, migration/rollback evidence, and production verification. Repository-only work that still requires production is at most `VERIFIED LOCALLY`.
|
||||
|
||||
Exactly one implementation item may be `IN PROGRESS`. As of this revision it is **JOBS-001**.
|
||||
Exactly one implementation item may be `IN PROGRESS`. As of this revision it is **JOBS-002**.
|
||||
|
||||
## Consolidated dependency order
|
||||
|
||||
@@ -48,6 +48,21 @@ Ordering differences from the suggested list:
|
||||
- The synthetic workload inventory/evaluation set (PROD-002) can proceed without production access and should inform routing and benchmarks early.
|
||||
- Production inventory, benchmark and rollout remain independent blockers; repository-side queue, policy and UX work continues without them.
|
||||
|
||||
## Immediate completion queue (2026-08-15)
|
||||
|
||||
This queue records the highest-value work that can proceed without production credentials, provider consent or a new product decision. It reuses the work packages below rather than creating duplicate implementations.
|
||||
|
||||
| Order | Immediate work | Owning package(s) | Current state and finish line |
|
||||
|---:|---|---|---|
|
||||
| 1 | Admin-only deployed-version indicator in the application header | DEP-001, VER-001 | Implemented with authenticated API and shell tests. The badge shows the CI deployment version and exposes the commit SHA in its accessible label/tooltip only for administrators; full regression, remote CI and deployment smoke remain. |
|
||||
| 2 | Lossless Career field persistence | CAREER-001 | Next implementable correction. Manual website/location/contact edits must round-trip exactly enough for user intent; extraction cleanup must not silently rewrite already reviewed values. Add controller and UI regression tests. |
|
||||
| 3 | CV contact/header/sidebar contrast correction | CAREER-002 | Queued. Apply renderer-scoped theme ownership for contact/headline text and rerun pathological HTML/PDF proof without shrinking typography. |
|
||||
| 4 | Dedicated Job Details parity and JOBS-002 closure | JOBS-002 | In progress. Finish any remaining legacy follow-up/application-package parity, dirty-edit behavior, tenant authorization and 375/768/1440 theme/keyboard/history/error/long-data verification. |
|
||||
| 5 | Cross-application contrast/accessibility pass | UX-002, UX-003, VER-001 | Queued after the scoped Career/CV corrections. Audit semantic alerts, secondary text, focus, loading/empty/error states and remaining hardcoded colors before documenting larger redesigns. |
|
||||
| 6 | Honest Free/Pro homepage and upgrade surfaces | PRODUCT-001 | Not started. Inventory existing claims first; do not invent pricing, limits or trial terms before billing configuration is real. |
|
||||
| 7 | Complete application action matrix and full regression | VER-001 | Not started. Populate incrementally, then run the complete backend/frontend/sidecar/E2E gates and accurately classify external production/provider checks. |
|
||||
| 8 | Tracking and blocker reconciliation | All | Keep this plan, progress, handoff, verification log and `BLOCKERS.md` aligned after every logical increment; remove stale CI/dependency statements only when current evidence proves them obsolete. |
|
||||
|
||||
## Requirement coverage index
|
||||
|
||||
| Source section | Covered by |
|
||||
@@ -617,7 +632,7 @@ Ordering differences from the suggested list:
|
||||
- **Blocker:** browser session was already finalized; three-width/theme/keyboard/Norwegian checks and production synthetic-account smoke remain.
|
||||
- **Evidence:** `docs/verification/career-001-career-workspace.md`; V-117–V-119 and V-164; focused Career/Profile 17/17, extraction backend 8/8, sidecar 22/22 and production build. State-aware actions/recent CVs are implemented, extraction polling no longer overwrites unsaved form state, and the Apply/Discard gate is unchanged.
|
||||
- **Commit:** `268b3a0` (`feat(career): clarify workspace actions`) plus the CAREER-002 polling checkpoint recorded in V-164.
|
||||
- **Remaining work:** browser and production gates only; live model-quality benchmarking and deeper builder interaction belong to CAREER-002.
|
||||
- **Remaining work:** correct lossless persistence for manually reviewed website/location/contact values, then run the browser and production gates. Live model-quality benchmarking and deeper builder interaction belong to CAREER-002.
|
||||
|
||||
### CAREER-002 — CV Builder interaction redesign and external research
|
||||
|
||||
@@ -635,7 +650,7 @@ Ordering differences from the suggested list:
|
||||
- **Blocker:** authenticated JobTracker three-width/theme/keyboard checks and production synthetic-variant smoke require their runtime environments.
|
||||
- **Evidence:** `docs/verification/career-002-cv-builder.md`; V-120–V-125 and V-165. Builder 21/21 and renderer/templates 25/25 pass plus build. Pathological Chromium output has zero horizontal offenders and produces a readable nine-page PDF without text shrinking.
|
||||
- **Commit:** `b58cc19`, `a5b74e0`, `2043349` plus the V-165 checkpoint.
|
||||
- **Remaining work:** authenticated application-browser/production gates and honest deployed DOCX capability check only. Public competitor-pattern research is complete; no authenticated competitor session is claimed.
|
||||
- **Remaining work:** correct header/sidebar contact contrast, then complete authenticated application-browser/production gates and the honest deployed DOCX capability check. Public competitor-pattern research is complete; no authenticated competitor session is claimed.
|
||||
|
||||
### MAIL-001 — Consolidated job-email hub and explicit sending
|
||||
|
||||
@@ -688,7 +703,7 @@ Ordering differences from the suggested list:
|
||||
- **Status:** `IN PROGRESS`.
|
||||
- **Blocker:** none after dependencies.
|
||||
- **Evidence:** V-158–V-162; `docs/verification/jobs-002-application-workspace.md`.
|
||||
- **Commit:** `bd5362c` (URL-owned list state); dedicated page commit pending.
|
||||
- **Commit:** `bd5362c` (URL-owned list state), `109745e` (canonical dedicated page/table/sidebar integration).
|
||||
- **Remaining work:** canonical `/jobs/:id`, row/card navigation, compact priority columns, richer job details, contextual links, sidebar cleanup and notification popover are implemented. Still required: confirm section dirty-edit behavior; browser widths/themes/keyboard/history/error/long-data checks; authorization regression and production smoke. Do not place every field in the table or duplicate workspace data.
|
||||
|
||||
### UX-003 — Kanban theme-state correction
|
||||
@@ -761,7 +776,7 @@ Ordering differences from the suggested list:
|
||||
- **Blocker:** final deployment verification depends on the remote CI/live environment.
|
||||
- **Evidence:** `docs/verification/dep-001-frontend-advisories.md`; V-137/V-141; audit 0 vulnerabilities, focused 24/24, full 190/190 and production build pass; Gitea run 609 passed complete pull-request CI in 4m20s after the stale browser assertion correction.
|
||||
- **Commit:** `b55a592` (pushed).
|
||||
- **Remaining work:** approved merge/deploy from `main`, production route smoke and update to `DONE` only after production verification.
|
||||
- **Remaining work:** the admin-only header version indicator is implemented on the release branch; run full/remote gates, merge/deploy from `main`, confirm the visible badge matches the CI run version and production commit, run route smoke, and update to `DONE` only after production verification.
|
||||
|
||||
### REL-001 — Production validation and remaining audit closure
|
||||
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
Updated: 2026-08-15
|
||||
|
||||
- **Exact current task:** finish CAREER-002 authenticated/multi-width browser regression, then run combined repository gates and continue the remaining programme.
|
||||
- **Last completed step:** hardened both CV renderers for long/multi-page content, corrected A4/Letter preview measurement, unified custom-section ordering, serialized stored-output actions, and completed public competitor-pattern research.
|
||||
- **Files currently modified:** CV renderer/resolver/template, Builder editor/list/helpers/tests and CV architecture/research/verification/tracking documentation.
|
||||
- **Commands already run:** CV renderer/templates backend 25/25; Builder frontend 21/21; optimized frontend build; real Chromium DOM/PDF pathological fixture.
|
||||
- **Test results:** focused backend/frontend and build pass. The pathological 14-role/75-skill fixture had zero horizontal overflow offenders and produced a nine-page 173,196-byte PDF with extractable final-page text. Full-suite and authenticated application-browser gates remain next.
|
||||
- **Exact current task:** finish and ship the prioritized admin-only deployed-version indicator, then correct lossless Career manual-field persistence and CV contact contrast before returning to JOBS-002 closure.
|
||||
- **Last completed step:** exposed configured build version/commit metadata through `/api/auth/me` only for administrators and added a compact responsive header badge with accessible version/commit text.
|
||||
- **Files currently modified:** auth bootstrap DTO/controller/test, application shell/App bootstrap/test, and master programme tracking/evidence.
|
||||
- **Commands already run:** focused Auth/System 36/36; focused AppShell 2/2; full backend 640/640; full frontend 53 suites/221 tests; optimized frontend build/TypeScript; diff check.
|
||||
- **Test results:** all listed local gates pass. Jest retains the documented force-exit/open-handle notice; prior CV pathological proof and recorded baselines remain valid.
|
||||
- **Services currently running:** none on task-owned ports 3000/5202. Playwright stopped its disposable API/Next servers. Pre-existing Docker services were not changed.
|
||||
- **Temporary files or processes:** no task-owned process is running and the failed disposable migration database was removed. Existing synthetic browser evidence/account and startup-created local backup remain documented. No provider account, real email, private content, paid service or production service was accessed.
|
||||
- **Production changes currently active:** none. No deployment, migration, provider connection/sync/send or production payload occurred.
|
||||
- **Rollback status:** downgrade `20260810080858_AddEmailDraftClientRequestId`, then `20260810075206_AddEmailDrafts`, before reverting draft commits; then follow the existing MAIL rollback order (`ee5ef7e`, `449faeb`, `123fc55`/`e9937ac`, ledger downgrade before `653f011`). No production migration/deploy/provider grant occurred.
|
||||
- **Uncommitted changes:** V-165 CV Builder/rendering/research checkpoint; no dependency/schema/config change.
|
||||
- **Uncommitted changes:** V-166 admin version indicator and tracking update; no dependency/schema/config/migration change.
|
||||
- **Known failures:** live deployment is not verified because PR deploy is intentionally skipped and the active branch is not approved for merge. Draft export/API/UI, full thread/category actions and non-Gmail review remain; existing accounts need re-consent and IMAP stays read-only. A clean full-chain SQLite apply fails in the pre-existing JT-019 migration before the new draft migration. Browser/provider/MariaDB/production unavailable or unverified; recovery scan performance is unmeasured at large ledger scale; Jest open handles; SEC-006 parser dependency work is still separately gated; parser isolation remains SEC-007.
|
||||
- **Exact next action:** commit/push V-165; run authenticated/mocked 375/768/1440 light/dark CV and combined JOBS/theme/admin/browser regression, then full repository gates.
|
||||
- **Work that can continue independently:** JOBS-002, PRODUCT-001 and VER-001. UX/JOBS production, MAIL provider mutations, SEC-006/007 and PROD packages retain their recorded external gates.
|
||||
- **Exact next action:** review, commit and push V-166; then fix the Career manual-value normalization boundary and CV contact contrast with focused regressions.
|
||||
- **Work that can continue independently:** the immediate queue in the master plan: Career lossless persistence, CV contrast, JOBS-002 closure, cross-app contrast/accessibility, PRODUCT-001 and VER-001. UX/JOBS production, MAIL provider mutations, SEC-006/007 and PROD packages retain their recorded external gates.
|
||||
- **Decisions still required from the user:** none for synthetic/code-inspected repository work. Any provider connection or send test, internet/package upgrades, private data, external/paid providers and production actions retain explicit approval/safety gates; SEC-009 retention/legal policy remains unresolved.
|
||||
|
||||
@@ -78,6 +78,8 @@ type MeResponse = {
|
||||
roles?: string[];
|
||||
plan?: "free" | "pro";
|
||||
entitlements?: { ai?: boolean; proThemes?: boolean };
|
||||
appVersion?: string;
|
||||
appCommitSha?: string;
|
||||
};
|
||||
|
||||
function breadcrumbsFor(path: string, t: (k: any) => string): string[] {
|
||||
@@ -348,6 +350,7 @@ function Shell({ jobPageSize, setJobPageSize, jobColumns, setJobColumns, themeMo
|
||||
onToggleDrawer={setMobileDrawerOpen}
|
||||
onNavigate={(to) => { setMobileDrawerOpen(false); navigate(to); }}
|
||||
user={{ email: me?.email, userName: me?.userName, displayName: me?.displayName || fullName || undefined, avatarImageDataUrl: me?.avatarImageDataUrl, roleLabel: isAdmin ? t("superAdmin") : t("user") }}
|
||||
buildMetadata={isAdmin && me?.appVersion ? { version: me.appVersion, commitSha: me.appCommitSha } : undefined}
|
||||
notificationsCount={notificationCount}
|
||||
onOpenNotifications={(anchor) => setNotificationAnchor(anchor)}
|
||||
onOpenSettings={() => navigate("/settings")}
|
||||
|
||||
@@ -36,3 +36,41 @@ test("notification bell exposes its unread count and keyboard-accessible action"
|
||||
expect(open).toHaveBeenCalledTimes(1);
|
||||
expect(open.mock.calls[0][0]).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
test("shows build metadata only when the caller supplies the admin-only badge", () => {
|
||||
const commonProps = {
|
||||
pageTitle: "Dashboard",
|
||||
breadcrumbs: ["Home"],
|
||||
pathname: "/dashboard",
|
||||
nav: [],
|
||||
navBottom: [],
|
||||
onNavigate: () => undefined,
|
||||
onToggleDrawer: () => undefined,
|
||||
drawerOpen: false,
|
||||
};
|
||||
|
||||
const { rerender } = render(
|
||||
<CssVarsProvider theme={getTheme("light") as any} defaultMode="light">
|
||||
<I18nProvider>
|
||||
<AppShell {...commonProps} buildMetadata={{ version: "2.4.1", commitSha: "abc1234" }}>
|
||||
<div>Content</div>
|
||||
</AppShell>
|
||||
</I18nProvider>
|
||||
</CssVarsProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("admin-version-badge")).toHaveTextContent("v2.4.1");
|
||||
expect(screen.getByLabelText("Application version 2.4.1, commit abc1234")).toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
<CssVarsProvider theme={getTheme("light") as any} defaultMode="light">
|
||||
<I18nProvider>
|
||||
<AppShell {...commonProps}>
|
||||
<div>Content</div>
|
||||
</AppShell>
|
||||
</I18nProvider>
|
||||
</CssVarsProvider>,
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId("admin-version-badge")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Badge,
|
||||
Box,
|
||||
Breadcrumbs,
|
||||
Chip,
|
||||
Divider,
|
||||
Drawer,
|
||||
IconButton,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
Toolbar,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
@@ -82,6 +84,7 @@ export default function AppShell({
|
||||
onToggleDrawer,
|
||||
drawerOpen,
|
||||
user,
|
||||
buildMetadata,
|
||||
notificationsCount,
|
||||
onOpenNotifications,
|
||||
onOpenSettings,
|
||||
@@ -100,6 +103,7 @@ export default function AppShell({
|
||||
onToggleDrawer: (open: boolean) => void;
|
||||
drawerOpen: boolean;
|
||||
user?: { email?: string; userName?: string; displayName?: string; avatarImageDataUrl?: string; roleLabel?: string };
|
||||
buildMetadata?: { version: string; commitSha?: string };
|
||||
notificationsCount?: number;
|
||||
onOpenNotifications?: (anchorEl: HTMLElement) => void;
|
||||
onOpenSettings?: () => void;
|
||||
@@ -234,6 +238,26 @@ export default function AppShell({
|
||||
|
||||
const nameForAvatar = user?.userName || user?.displayName || user?.email;
|
||||
const initials = initialsFrom(nameForAvatar);
|
||||
const buildVersion = buildMetadata?.version.trim();
|
||||
const buildCommit = buildMetadata?.commitSha?.trim();
|
||||
const buildBadge = buildVersion ? (
|
||||
<Tooltip title={buildCommit ? `Version ${buildVersion} • commit ${buildCommit}` : `Version ${buildVersion}`}>
|
||||
<Chip
|
||||
data-testid="admin-version-badge"
|
||||
aria-label={`Application version ${buildVersion}${buildCommit ? `, commit ${buildCommit}` : ""}`}
|
||||
label={`v${buildVersion}`}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{
|
||||
maxWidth: 150,
|
||||
fontWeight: 700,
|
||||
fontVariantNumeric: "tabular-nums",
|
||||
bgcolor: "background.paper",
|
||||
"& .MuiChip-label": { overflow: "hidden", textOverflow: "ellipsis" },
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null;
|
||||
|
||||
const [userMenuAnchor, setUserMenuAnchor] = useState<null | HTMLElement>(null);
|
||||
const userMenuOpen = Boolean(userMenuAnchor);
|
||||
@@ -281,6 +305,7 @@ export default function AppShell({
|
||||
Jobbjakt
|
||||
</Typography>
|
||||
</Box>
|
||||
{buildBadge}
|
||||
</Box>
|
||||
|
||||
{user ? (
|
||||
@@ -358,6 +383,7 @@ export default function AppShell({
|
||||
flex: { xs: "1 1 100%", md: "0 1 auto" },
|
||||
}}
|
||||
>
|
||||
{buildBadge}
|
||||
<IconButton
|
||||
color="secondary"
|
||||
size="small"
|
||||
|
||||
Reference in New Issue
Block a user