Skip to content

build(deps): bump node from 20-alpine to 26-alpine#2

Closed
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/docker/node-26-alpine
Closed

build(deps): bump node from 20-alpine to 26-alpine#2
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/docker/node-26-alpine

Conversation

@dependabot
Copy link
Copy Markdown

@dependabot dependabot Bot commented on behalf of github May 20, 2026

Bumps node from 20-alpine to 26-alpine.

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

Bumps node from 20-alpine to 26-alpine.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 26-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot @github
Copy link
Copy Markdown
Author

dependabot Bot commented on behalf of github May 20, 2026

Labels

The following labels could not be found: area/docker, dependencies. Please create them before Dependabot can add them to a pull request.

Please fix the above issues or remove invalid values from dependabot.yml.

@dependabot @github
Copy link
Copy Markdown
Author

dependabot Bot commented on behalf of github May 20, 2026

Looks like node is no longer a dependency, so this is no longer needed.

@dependabot dependabot Bot closed this May 20, 2026
@dependabot dependabot Bot deleted the dependabot/docker/node-26-alpine branch May 20, 2026 19:34
DeveloperCodeBase added a commit that referenced this pull request May 22, 2026
Owner sent 5 screenshots after R1.3 failed manual smoke. Filled the
audit table with concrete findings and authored the R1.4 spec.

DEFINITIVELY CONFIRMED BUGS (3):

  Bug #1 (B4) — sidebar drawer renders as 3 horizontal pills instead
  of a vertical list. Root cause: styles.css:3598-3641 transforms
  `.side-nav` to horizontal flex with !important at <720px — designed
  for the legacy inline `.dash` sidebar, but the Sheet drawer's
  RoleSideNav inherits the rule. Fix: scope to `.dash .side-nav` +
  add `.appshell-sidebar-drawer .side-nav` override.

  Bug #2 (B5) — mock role-avatar "نر" leaks on landing + login for
  anonymous visitors. Phase 14.8 fixed user-name fallback but missed
  user-avatar. Fix: shared.tsx Nav user-btn — render generic icon
  when !auth.user, never the mock role.avatar.

  Bug #3 (Brand) — logos broken in screenshot 3 because the screenshot
  predates the logo file commit (deploy-timing artifact, not code).
  Fix: one fresh `up` to rebake nginx with the new public/ assets.

UNVERIFIED (need more screenshots before action):

  B1 sticky navbar — no scroll-down screenshot. R1.3 assertion only
  checked computed style, never real iOS scroll behaviour.
  B2 login layout — chips ARE 2-col (works), but navbar visibility on
  /login is ambiguous from screenshot 5.
  B3 dashboard/profile responsive — not captured in this set.

DEFERRED (per prior agreement):

  B6 classroom mobile — Phase D R1 (LiveKit ground-up rewrite).

The R1.4 spec details for each confirmed bug:
  - exact screenshot link / evidence
  - what the R1.3 assertion verified (and why it missed the real issue)
  - exact file:line fix
  - D12 5-point assertion (DOM, computed style, viewport position,
    no overlap, pixel-diff ≤ 0.001)
  - visual baseline path

PAUSING per D13. Three owner approvals requested in the spec before
I write any code:
  1. B4 CSS scoping approach (legacy `.dash` retain vs delete)
  2. B5 avatar fallback design (icon vs glyph)
  3. Visual baseline review workflow

R2 stays gated until R1.3 + R1.4 finally pass owner manual smoke.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 22, 2026
…nual smoke

R1.4 closed 3 of the 6 bugs the owner manual-smoke found in R1.3:
  - Bug #1 (B4): sidebar drawer now renders vertically with section
    headers instead of 3 horizontal pills
  - Bug #2 (B5): anonymous avatar is a generic person icon, never
    leaks the role-mock initials "نر" / "AA" / etc.
  - Bug #3 (Brand): logos serve over HTTP 200 + decode (naturalWidth>0)

Automated grid post-fix:
  R1.1 13/13 · R1.2 9/10+1 intentional skip · R1.3 17/17 · R1.4 7/7
  → 46 pass, 1 skip, 0 fail across 4 specs

Per R1.3-D13, automated green ≠ shipped. PAUSING for owner manual
smoke on a real device + incognito tab. Six manual checks listed in
the review (3 fixed, 3 still unverified):
  1. drawer vertical with section headers
  2. anon avatar = generic icon on /
  3. anon avatar = generic icon on /login
  4. JDO logo renders in footer (not broken-image)
  5. sticky navbar on scroll (UNVERIFIED — no screenshot yet)
  6. dashboard mobile reachable now that drawer works (UNVERIFIED)

What's NOT done until owner says go:
  - R2 retire @ts-nocheck (gated)
  - Visual baselines committed (UPDATE_BASELINES=1 workflow exists,
    PNGs not yet owner-approved)
  - B1/B3 deeper fixes (gated on owner confirmation they're broken)
  - B6 classroom mobile (deferred to Phase D R1)

D12 5-point contract satisfied for the 3 fixed bugs (point 5 gated).
D13 (manual smoke is formal gate) honoured by stopping here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 24, 2026
…te-limit edge)

R7.7 review doc filed this as a known follow-on. R7.3's regression
sweep hit it (test #2 instead of the usual #7): prior visual specs
in the same minute already consumed some of the api's 10/min login
bucket, so the 7s-spaced 7th login landed in a contended window.

Bumping to 7s gives 10 roles × 7s = 70s — pushes the 7th login
outside the 60s rolling window even when the bucket has been touched
by a prior spec.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 24, 2026
…agnosis

3rd attempt at gate-a-role-routing failed at admin (test #3) after
~5 min cooldown. Failure point moves across attempts (#2 instructor,
#1 student, #3 admin) but failure mode is identical (login form
waitForURL timeout). Diagnosis: rate-limit bucket depletion from
today's cumulative login-touching test runs — NOT a R7.3 regression.

Verified R7.3 code is correct:
- R7.3 per-fix spec A.1 authed test PASSES (same login helper)
- Lighthouse /login re-measured to 100/100/96
- Production bundle contains both 'منوی حساب' + 'منوی کاربر'

R7.3 verdict stays: functionally green, awaiting D13 + future
cooldown re-run for the role-routing 10/10 confirmation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 24, 2026
…d environmental

Post-cooldown re-run of gate-a-role-routing alone: 10/10 PASS
(student 2.2s, instructor through super_admin each ~8.9s). Confirms
the spec is correct + R7.1+R7.2 didn't break the role-routing flow.

The earlier failure during the regression sweep was the documented
D32 rate-limit infra flake — cumulative login attempts within the
60s window depleted the api's 10/min bucket. The 7s inter-test pause
(R7.3 silent-fix #2) is correct in isolation; only contended bucket
state at the moment of sweep firing caused the 1/10 outcome.

R7.1+R7.2 regression verdict: 7/8 specs clean + 1 environmental
test fragility (R1.1 skip-link Tab focus, NOT a code regression —
AppShell skip-link is unchanged + verified by curl).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 26, 2026
D61 logged in PHASE_A_DECISIONS.md:
- Workflow discipline reminder (retrospective lesson #1):
  every Phase B sub-R = memo → ack → code → spec → deploy →
  D29 → D13 → close, no skip
- Performance budget reminder (retrospective lesson from R7.1.5):
  R1 admin pages lazy-loaded, admin-academic manualChunks bucket,
  main bundle delta < 50KB target, asset weight check in review doc

R1 memo refactored with new «🚧 Binding constraints per D61» section:
- Constraint #1 — workflow discipline (verbatim, no skipping)
- Constraint #2 — performance budget (lazy + manualChunks +
  bundle delta < 50KB + R1.5 escape hatch if exceeded)
- Notes what these constraints do NOT change (LoC estimate, locked
  Q-answers, timeline)

NEW: docs/PHASE_B_R0_5_MEMO.md
Short DRAFT memo for the docs-only R0.5 sub-R (MIGRATION_POLICY.md).
Covers:
- 11 sections of the policy doc with per-section LoC estimates
  (total target ~160 LOC, matches owner estimate)
- What the doc INCLUDES (Phase A dormant-table caveat, R1 4-level
  hierarchy as greenfield illustration, code-shape pseudo-snippets,
  Phase A R4 enforcement note, 5-point migration commit checklist)
- What the doc EXCLUDES (no migration files, no tooling impl,
  no API versioning strategy duplication, no backfill scripts)
- Why R0.5 vs R1.A (Q1.a rationale + workflow discipline)
- Verification plan (owner reads + acks; no Chrome Extension needed)

No code starts until R0.5 doc owner-acked.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 27, 2026
apps/web/src/api/endpoints.js:
- catalogApi.listSchools added; existing listFaculties gains optional
  {schoolId} filter (additive — undefined arg behaves identically).
- NEW academicAdminApi block: full CRUD across all 4 levels:
    listSchools / getSchool / createSchool / updateSchool / deleteSchool
    listFaculties (with ?schoolId=) / getFaculty / create / update / delete
    listDepartments (with ?facultyId=) / get / create / update / delete
    listPrograms (with ?departmentId= & ?degreeLevel=) / get / create /
                                                        update / delete
  19 entries total, mapping 1:1 to the NestJS endpoints from Commits B-D.

NEW apps/web/src/pages/admin/_shared/:
- CrudDialog.tsx: shared create/edit modal with form submission, busy
  state, Escape key close, aria-modal a11y, backdrop click dismiss.
- FormField.tsx: text/number/date input with label-above-input layout,
  helper text, error message slot, aria-invalid + aria-describedby
  wiring. SelectField variant for FK pickers (schoolId on Faculty,
  facultyId on Department, departmentId on Program).
- ConfirmDelete.tsx: alertdialog for soft-delete confirmation with
  danger button styling.

Extracted to avoid 4× duplication across SchoolsPage, FacultiesPage,
DepartmentsPage, ProgramsPage (Commits G + H).

R1 chain: A → B → C → D → E (API e2e green) → F (this) → G+H+I+J+K
still ahead. Bundle impact: zero on / (admin routes will be
React.lazy() in Commit I per D61 Constraint #2).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 27, 2026
apps/web/src/pages/admin/SchoolsPage.tsx (~250 LOC):
- Lists all schools with name, nameEn, shortCode, slug, faculty count
- Shows empty-state + "افزودن اولین دانشکده" CTA when no rows
- + افزودن دانشکده button opens CrudDialog in create mode
- Click pencil icon → CrudDialog in edit mode (pre-populated)
- Click trash icon → ConfirmDelete modal → soft-delete with cascade
  warning if faculties exist underneath
- All mutations refetch the list after success (kept consistent with
  server-side audit log)
- isAdmin gate: only admin / super_admin sees the + / edit / delete
  buttons; non-admin viewers see read-only table

Form fields (per Q4.a spirit dual-column design):
- nameFa (required, max 160) — labeled "نام فارسی"
- nameEn (optional, max 160, dir=ltr) — labeled "نام انگلیسی"
- slug (required, max 64, dir=ltr) — labeled "شناسه" with helper
- shortCode (optional, max 32, dir=ltr) — labeled "کد اختصاری"
- sortOrder (number) — labeled "ترتیب نمایش"
- description (optional, max 2000) — labeled "توضیحات"

Validation:
- nameFa + slug required (client-side check before submit)
- Server errors surfaced via formError state (e.g., P2002 "slug
  already in use" maps to BadRequest with friendly message from
  Commit B)

Uses _shared primitives from Commit F:
- CrudDialog (modal + form wrapper)
- FormField (label + input + helper + error)
- ConfirmDelete (alertdialog for soft-delete)

Bundle impact: zero on / (this page is React.lazy() in Commit I).
Will land in admin-academic chunk per D61 Constraint #2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 27, 2026
…(D62)

Three full-CRUD admin pages mirroring the SchoolsPage shape:

FacultiesPage (~240 LOC):
- List + CRUD + soft-delete, with optional ?schoolId= filter chip
- SelectField for school FK picker (with emptyLabel "(بدون دانشکده)"
  to support detach)
- Same fields as Faculty model: name (existing Persian SoT), nameEn,
  shortCode, schoolId, slug (create-only), description
- Empty-state when no rows
- Read-only mode if non-admin

DepartmentsPage (~190 LOC):
- List + CRUD with optional ?facultyId= filter
- Required facultyId picker on create (Department.facultyId is NOT NULL)
- facultyId hidden after create (can't reparent existing rows in R1
  per scope; admin must delete+recreate or PATCH via API)
- Same shape as Department: name, nameEn, shortCode, slug, description

ProgramsPage (~220 LOC):
- List + CRUD with optional ?departmentId= and ?degreeLevel= filters
- degreeLevel SelectField with 4 options (bachelor/master/phd/certificate)
  labeled in Persian (کارشناسی/کارشناسی ارشد/دکتری/گواهینامه)
- Required departmentId picker on create
- durationSemesters number field (optional)
- Same additive cols + name + nameEn + shortCode + slug + description

All three use _shared/{CrudDialog, FormField, SelectField, ConfirmDelete}
from Commit F. Each page exports default for React.lazy() in Commit I.

Bundle impact stays on the admin-academic chunk (Commit I will wire
the manualChunks bucket). Main bundle delta target: < 50 KB per D61
Constraint #2.

R1 chain: A → ... → G (Schools) → H (this) → I (sidebar + router +
guard) → J (D12+D18 specs) → K (review doc + post-deploy smoke).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 27, 2026
…(D62 + D61 Constraint #2)

Router (apps/web/src/router.tsx):
- 4 React.lazy() imports for SchoolsAdminPage / FacultiesAdminPage /
  DepartmentsAdminPage / ProgramsAdminPage (each their own chunk
  boundary)
- 4 new route registrations at /admin/schools, /admin/faculties,
  /admin/departments, /admin/programs

Vite config (apps/web/vite.config.js):
- manualChunks switched from object form to function form. Function
  routes any file under /pages/admin/ to the "admin-academic" chunk
  (catches both the 4 page files + the _shared primitives from Commit
  F). React + radix vendor buckets preserved with id-pattern matching.
- Per D61 Constraint #2: main bundle delta target < 50 KB. Admin
  pages now land in their own chunk separately, never loaded for
  students/instructors/other roles.

Sidenav (apps/web/src/sidenav.tsx):
- admin role gets a new "ساختار آکادمیک" section header + 4 items:
  admin/schools (دانشکده‌ها), admin/faculties (هیأت‌ها),
  admin/departments (گروه‌ها), admin/programs (برنامه‌ها)
- Existing pre-R1 "schools" + "faculty" items deprecated by name +
  route (replaced with admin/* equivalents that hit the new full-CRUD
  pages). Old marketing /schools + /faculty routes remain reachable
  via go() — not removed in R1 (out of scope).

Access guard: not added as a separate middleware in this commit
because the *server-side* @roles("admin") guard on every mutation
endpoint (Commits B-D) already enforces. Client-side, the pages
themselves render read-only mode for non-admin roles via
useRole().role.id check at the top of each page. Defense in depth.

R1 chain: A → ... → H (3 pages) → I (this) → J (D12+D18 specs next)
→ K (review doc + post-deploy smoke).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 27, 2026
D64 logged: R1 D13 owner real-device smoke 8-step PASS, R1 closed.
- Admin sidebar 4 items, full CRUD, cascade soft-delete, nested
  breadcrumb, access guard all verified
- Bundle delta target met: admin-academic-DCwo3HC3.js = 37 KB
  separate chunk, main index-Bch2wDJC.js unaffected for non-admin

NEW: docs/PHASE_B_R2_MEMO.md — Phase B R2 candidate scope memo.

R1 lessons applied pre-memo-write:
- Lesson #1 (schema discovery): inspected Cohort + Enrollment +
  Course existing models BEFORE writing the memo. Result: Cohort
  exists with slug/program/dates; CourseOffering truly greenfield;
  blast radius = 1 downstream model (Enrollment).
- Lesson #2 (4-model assumption check): single-model migration
  confirmed, no surprise dependencies.
- Lesson #3 (perf budget per D61 #2): extend existing admin-academic
  chunk (no new bucket); expected +10-15 KB admin / 0 main.

R2 scope decision: Cohort → CourseOffering (vs Course/Module/Lesson
or Profile/Student/Instructor alternatives). Rationale:
1. First practical MIGRATION_POLICY §3+§5 dual-write usage
2. Compass §Phase B-aligned
3. Reasonable single sub-R scope ~1,880 LOC / 5-7 days
4. Validates §10 rollback caveat discipline in advance
5. Doesn't block identity models (R3+ track)

4 open questions for owner ack (Q1 scope confirm, Q2 richer offering
shape, Q3 dual-write timing all-vs-split, Q4 cohort UI dual-vs-merge).
Defaults: Q1.a / Q2.b / Q3.a / Q4.a.

10 atomic commits planned A-J (one fewer than R1 by reusing R1's
_shared admin components).

NO R2 code starts until Q1-Q4 ack.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 27, 2026
…achine controls (D65)

apps/web/src/pages/admin/OfferingsPage.tsx (380 LOC) — admin surface
for /admin/offerings. Routes lazy-loaded in Commit I (router.tsx) so
the chunk joins admin-academic.{hash}.js per D61 Constraint #2.

Reuses R1 _shared primitives (CrudDialog + ConfirmDelete + FormField)
with three R2-specific additions:

1) Status pill — renders the OfferingStatus enum as a colored pill
   per row: SCHEDULED / OPEN / ACTIVE / COMPLETED / CANCELED with
   Persian labels (زمان‌بندی شده / ثبت‌نام باز / در حال برگزاری /
   پایان‌یافته / لغو شده). CSS classes pill-status pill-{status}
   for theming (CSS lives in styles.css from Phase A).

2) Inline transition controls — buttons rendered per row showing
   ONLY the legal next-states per ALLOWED_TRANSITIONS map (mirror
   of CourseOfferingsService.ALLOWED_TRANSITIONS, kept in sync by
   convention + Commit I spec assertion).
   • SCHEDULED row: shows [→ ثبت‌نام باز] [→ لغو شده]
   • OPEN row: shows [→ در حال برگزاری] [→ لغو شده]
   • ACTIVE row: shows [→ پایان‌یافته] [→ لغو شده]
   • COMPLETED + CANCELED rows: no transition buttons (terminal)
   Click → ConfirmDelete-style confirmation modal (reused component
   for symmetry) → POST /transition. Server-side state-machine still
   enforces; client filter is UX prevention not security.
   Illegal transition rejection message surfaced via window.alert
   verbatim from server (the allowed-from-current list per D65
   R2-Reminder-1).

3) Mode + capacity columns — mode rendered as Persian label
   (همزمان / ناهمزمان / ترکیبی); capacity nullable.

Cohort linkage shown in «پیشینه» column: «از Cohort» pill if the
offering was minted by LegacySyncService (legacyCohortId present);
«—» if greenfield.

CRUD form: shared with create + edit. Create form prompts for
programId (dropdown from listPrograms) + slug + nameFa + nameEn? +
shortCode? + mode + startDate? + endDate? + capacity? + description?
Edit form omits programId + slug (immutable post-create).

Delete confirmation body explicitly mentions cascade to linked
Cohort soft-delete if legacyCohortId present (matches LegacySyncService
behavior verified in API smoke).

NEXT: Commit H — CohortsPage «Legacy» banner + warning + «migrate
to offering» CTA.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 27, 2026
…(D65)

apps/web/src/pages/admin/CohortsPage.tsx (175 LOC) — admin surface for
/admin/cohorts. Lazy-loaded; joins admin-academic chunk per D61
Constraint #2.

Per MIGRATION_POLICY §6 + D65, this surface stays alive during the
Sunset window (drop no sooner than 2026-12-31) but is intentionally
READ-ONLY in the admin UI:
  - no «+ افزودن» button (new cohorts still possible via API for
    back-compat but the admin UI pushes users toward Offerings)
  - no edit/delete buttons (existing CRUD still works via API; admin
    UI strips them to discourage continued use of the legacy surface)

Three R2-specific bits:

1) Prominent «Legacy» banner — role="alert" so screen readers
   announce it on page load. Amber color scheme (var(--amber-600)
   left border + amber background) for visual urgency without being
   alarmist. Banner text:
   • Explains the migration (new structure: state machine + mode +
     capacity + future instructor link).
   • Counts: X of Y cohorts already linked, (Y - X) pending.
   • CTA: «رفتن به دوره‌های ارائه‌شده» → go("admin/offerings").
   • Drop date callout: «پس از 2026-12-31 حذف می‌شود».

2) Migration status column — each row shows either:
   • «✓ منتقل شد» (linked: upgradedToOfferingId present) +
     «→ دوره‌ی متناظر» button → /admin/offerings
   • «در انتظار انتقال» (orphan) +
     hint «اولین ویرایش، انتقال خودکار را فعال می‌کند»
     (matches LegacySyncService.onCohortUpdated behavior verified
     in API smoke step 5)

3) Empty state — encourages migration: if 0 cohorts left, button
   says «رفتن به ساختار جدید — دوره‌های ارائه‌شده» (only path).

Banner styling uses inline style for brevity (one-off component;
keeping it self-contained avoids polluting styles.css with banner
classes that will be removed in 2027). data-legacy-banner="cohorts"
attribute makes the banner addressable for the D12 spec assertion
in Commit I.

NEXT: Commit I — D12 + D18 specs (state machine flow assertion +
dual-write trace + illegal-transition rejection + legacy banner
visibility) + router.tsx registration for both new routes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 27, 2026
…nder-1)

apps/web/src/router.tsx:
  - Lazy imports for OfferingsAdminPage + CohortsAdminPage (joins
    admin-academic chunk per D61 Constraint #2; main bundle delta
    target 0 KB).
  - Route registrations: /admin/offerings + /admin/cohorts via
    <RouteShell Component={...} /> matching R1 pattern.

apps/web/tests/visual/phase-b-r2-course-offerings.spec.ts (NEW, ~330 LOC):

D12 visual contract (3 tests):
  • /admin/offerings table renders status pill column header + the
    seeded e2e offering row, with .pill-status pill visible carrying
    Persian label «زمان‌بندی شده» and transition button .btn-transition-open
  • /admin/cohorts renders [data-legacy-banner=cohorts] with
    role="alert", containing «منسوخ» + «2026-12-31»
  • Admin sidebar contains both new entries («دوره‌های ارائه‌شده»
    + «گروه‌های آموزشی (Legacy)»)

D18 flow assertions per D65 R2-Reminder-1 (5 tests):
  1. Happy path SCHEDULED → OPEN → ACTIVE → COMPLETED chained
     transitions all return 200, status field updates correctly
  2. Illegal OPEN → SCHEDULED rejects 400 with backend's
     "Allowed from OPEN: [ACTIVE, CANCELED]" message verbatim
     (asserts presence of ACTIVE + CANCELED in message body)
  3. Soft-delete allowed at any status — verifies both SCHEDULED and
     ACTIVE delete return 200 (terminal COMPLETED implicitly covered
     by happy-path test)
  4. Dual-write: POST /v1/cohorts populates upgradedToOfferingId +
     mints a CourseOffering with legacyCohortId backlink
  5. Cascade: cohort soft-delete causes linked offering to also
     soft-delete (GET offering returns 404 post-delete)
  6. GET /v1/cohorts emits Sunset / Deprecation / Link headers

Spec tagged @phase-b-r2 for selective playwright filtering. Uses live
deployment (no API mocks); each test creates + cleans up its own row.
ADMIN_PASSWORD reads from SEED_ADMIN_PASSWORD env or falls back to
the seed default "ChangeMe!2026".

NEXT: Commit J — review doc + bundle delta measurement + final ping.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 27, 2026
…hunk

Per D66 owner directive. Path A surgical fix for D61 Constraint #2
violation detected post-Commits-F-I bundle measurement.

Symptom (live, post-deploy blkyrr57e):
  admin-academic-DIYsnFTw.js: 37 KB → 416 KB (+379 KB)
  index-BSNY3WwB.js: 366 KB → 2 KB (shifted, main imports from admin)
  Critical: admin-academic-*.js is now <link rel="modulepreload"> in
  every served HTML — anonymous visitors download 416 KB of admin code
  on cold cache, undoing Phase A R7.1.5 image-optimization win.

Root cause: manualChunks rule "any /pages/admin/* → admin-academic"
matched /pages/admin/_shared/*.tsx primitives (CrudDialog +
ConfirmDelete + FormField). Some _shared symbol or transitive utility
ended up in both main's import graph + admin pages' import graph;
Vite hoisted it into admin-academic to dedupe + made main eager-import
from there, escalating admin-academic from lazy to eager.

Fix (3 LOC effective change):
  if (id.includes("/pages/admin/_shared/")) return undefined;
  if (id.includes("/pages/admin/")) return "admin-academic";

_shared now falls through to Vite's default chunker — typically merged
into main (small + shared) OR split into a small chunk based on
optimal-shared-dep analysis. Either way, admin-academic returns to
admin-page-only + lazy-loaded.

Pass criteria (verified post-deploy):
  - main index-*.js delta < 50 KB vs pre-R2 (Constraint #2 budget)
  - admin-academic-*.js < 55 KB (proactive flag if 45-55)
  - admin-academic NOT in modulepreload on / + /login + /programs HTML

If Path A fails to meet criteria (e.g., _shared bloats main >50KB),
fallback Path B: 3-bucket split with explicit admin-shared chunk.

Lesson per D66 directive: future Phase B sub-Rs that add admin pages
MUST report post-deploy bundle measurement in their review doc. The
manualChunks rule surface is fragile — small import graph changes can
flip chunk boundaries dramatically.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 27, 2026
…nt/Instructor + 2 state machines)

D67 entry in PHASE_A_DECISIONS.md (R2 D13 PASS, 8-step owner smoke
confirmed, R2 closed) + D66 entry filled in (R2 admin-academic chunk
leak fix Path A→B→D iteration history with the hard lesson logged
for Phase B+ sub-Rs).

PHASE_B_R3_MEMO.md DRAFT (~280 LOC) — Phase B Identity track:

SCHEMA DISCOVERY (R1 D63 + R2 lesson #1):
- User EXISTS (basic identity from Phase A)
- Profile/Student/Instructor/StudentApplication/InstructorApplication
  all GREENFIELD per MIGRATION_POLICY §2
- R2 CourseOffering.instructorId additive wire (§4) — finally wiring
  the R2 Q2 deferral

SCOPE:
- 5 new models + 4 enums (StudentStatus / InstructorStatus /
  InstructorRank / AppStatus)
- 2 state machines (StudentApplication + InstructorApplication share
  AppStatus enum, parallel transition maps): SUBMITTED → UNDER_REVIEW
  → INTERVIEW → ACCEPTED → ENROLLED, with REJECTED/WITHDRAWN terminal
- ACCEPTED → ENROLLED side effect: creates User (if not exists) +
  Student/Instructor + Enrollment row for default offering
- 4 admin pages (Applications inbox / Students / Instructors /
  Profile) — each as own lazy chunk per R2 D66 Path D pattern
- NotificationLog stub (Compass §Phase B minimal): logs notification
  events on key transitions; real sender deferred to R-Notif post-B

R1+R2 LESSONS APPLIED:
- Lesson #1 (schema discovery pre-memo): done, found all greenfield
  except R2 additive
- Lesson #2 (bundle vigilance): per-route chunking mandatory, no admin
  manualChunks bucket. Post-deploy bundle measurement in review doc
  REQUIRED. Modulepreload verification on / + /login + /programs.
- Lesson #3 (state machine pattern from R2): ALLOWED_TRANSITIONS map
  in service layer (not controller), isLegalTransition() exported for
  tests, illegal transition 400 with Allowed-from-current message,
  soft-delete legal at any status.

4 QUESTIONS for owner:
- Q1: single R3 vs split R3.a (Identity models) + R3.b (state machines)
  RECOMMENDED Q1.b split — state machines deserve their own atomic
  shipping window for D18 coverage clarity (R2 lesson)
- Q2: Profile cardinality 1:1 strict vs lazy 1:0..1
  RECOMMENDED Q2.a strict — predictable invariants, O(N) backfill
- Q3: CourseOffering instructor cardinality (single FK / array /
  junction table)
  RECOMMENDED Q3.a single instructorId now — junction additive in R4
  if team-teaching becomes real
- Q4: StudentApplication userId nullable until signup vs require-
  account-first
  RECOMMENDED Q4.a nullable — lower friction for Iranian applicants

ESTIMATED: ~4,450 LOC if single R3; ~2,800 + ~1,650 if split.
Timeline: 7-10 days single, 4-5 days each split.

OUT OF SCOPE: actual email/SMS (NotificationLog stub only), avatar
upload pipeline, OAuth/SSO, applicant-facing self-service form.

No R3 code starts until owner Q1-Q4 answers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 28, 2026
Owner acked R4: Q0.a + Q1.a-Q5.a + dual-write forward-only.

Locked + unaffected: Q0.a (nullable courseId widening, two-shape model),
Q1.a (targetOfferingId), Q3.a (course-level + partial unique), Q4.a
(full admin page), Q5.a (resultingEnrollmentId), dual-write forward-only
(offeringId only, no cohort back-write — respects Sunset 2026-12-31).

⚠️ Q2 implementation HOLD — Commit A paused (stop triggers #1/#2/#6):

Schema + CODE discovery found the memo's Q2.a assumption «existing
status values already match the enum» is incorrect:
- Data: existing Enrollment.status is lowercase (active/completed/
  dropped/withdrawn); Postgres enum is uppercase → direct cast fails
- Code: existing enrollments.controller.ts (Phase-7, RBAC-gated) uses
  lowercase strings throughout — STATUSES const, @isin DTOs, self-enroll
  create, and the nuanced status-change RBAC (admin=any, instructor=
  completed-only, owner=withdrawn/dropped). Converting to Postgres enum
  rewrites all of it + needs a data migration.

→ Q2.a-as-stated (Postgres enum) is a §3 modification (existing-model
change + data migration + existing-code rewrite), NOT the §4-additive
«+60 LOC» the memo estimated. Trips #6 (existing-data migration
ambiguity → STOP before proceeding) + #1 + #2.

Recommended: Q2-via-service-layer — keep status as String (storage
unchanged, zero migration, zero existing-code rewrite, zero prod-data
risk); deliver the state-machine INTENT at the service layer in the new
R4 transition endpoint (ALLOWED_TRANSITIONS + illegal-400, same as
R2/R3.b LOGIC; the Postgres enum there was just greenfield storage).

This is exactly what Phase B lesson #1 (schema discovery pre-code) +
the D72 «ack ≠ ground truth on internals» lesson exist to catch —
flagged before writing a migration that would fail on the owner's
production data during their deploy step.

Commit A paused pending owner Q2 confirm. Everything else ready.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DeveloperCodeBase added a commit that referenced this pull request May 31, 2026
Starting Commit C surfaced a memo-missed dependency (stop-triggers
#1+#2): the anon student form needs a program picker + a valid
programId, but GET /v1/programs is authed and the /programs marketing
page is static — no public source of real program IDs.

Resolution (Option A): add @public @Throttle GET /v1/programs/public
?tenantSlug= -> [{id,slug,name,nameEn,degreeLevel}], active-only,
tenant-scoped. ~20 LOC backend in the "frontend" Commit C (necessary,
not optional — D49/D81-family exception). Public catalog data is
non-sensitive by nature (unlike application PII). Reusable primitive
for future dynamic marketing / catalog browse / deep-link apply.

Lesson: anon/public-feature discovery must check ALL data dependencies
for public-accessibility, not just the primary models.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

0 participants