Sandy & Jake standup · all-assignee view · grouped by Linear status · 87 active · 7 done this week
After testing a feature, click Mark Verified to move it to Done.
immigr8.net (apex) + www.immigr8.net as custom domains on immigr8-landing Render service via Render dashboard104.21.35.130 / 172.67.222.144 proxied via CF) to point to Render's CNAME (typically <service>.onrender.com or Render's flex IP)curl -sI https://immigr8.net/favicon.svg returns 200 with image/svg+xml content-typecurl https://immigr8.net/privacy, /terms, /subprocessors all return 200Immigr8/legacy-wordpress-backup-2026-MM-DD.tar.gzimmigr8 site subscriptionreference-entity-matrix.md) to remove Kinsta from Immigr8's vendor list~/.claude/projects/-Users-imac/memory/vision-immigr8.md to remove WordPress as a "still live" systemcustomDomains: [name: "immigr8.net", name: "www.immigr8.net"] (or equivalent) to the immigr8-landing service block in landing/render.yamlrender.yaml's landing service block for consistencychore(render): declare immigr8.net + www custom domains on landing service/Users/imac/Developer/immigr8/CLAUDE.md — § Legacy Stack (WordPress — Still Live) → mark as "Decommissioned MM/YYYY"/Users/imac/Developer/immigr8/README.md (if it references the WP stack)~/.claude/projects/-Users-imac/memory/vision-immigr8.md — remove "WordPress migration" references; reframe as completeddocs/product-analysis.md + docs/rebuild-plan.md — mark WP phases as completereference-entity-matrix.md to remove WP/Kinsta from Immigr8 vendor list (paired with IMMIGR8-NNN cancellation ticket)POST /api/submissions (and any other student-creating path) calls a new findOrCreateStudent({firstName, lastName, dateOfBirth?, countryOfOriginId?}) helper that searches for an existing match using normalized name + (optional) DOB + (optional) country before creating new. Defined match keys + tie-breaker docs land in apps/api/src/lib/student-matching.ts with unit tests./settings/students/duplicates (platform_admin + platform_team) lists candidate dup groups (same shape as UserDuplicates). Each group shows the records + their submissions + a "Merge into" action. Soft-merge sets students.merged_into_id and re-points submissions.student_id to the canonical record; deleted students get deleted_at set, not hard-deleted.apply-student-dedup-merge.ts runs in dry-run mode first, produces a report at assets/raw/<date>-student-dedup-candidates.csv for Sandy/Jake to review BEFORE any writes. Live mode requires --apply flag.b05e0404 and e93f875b collapse to one canonical record with both submissions attached. View student record link from either submission reaches the same /students/<id> page.pnpm tsx apps/api/src/db/scripts/seed-dr-congo-manual.ts /path/to/content.jsonimmigr8-api-staging and production API servicepg_dump --format=custom both immigr8-db-staging and immigr8-hq-dbimmigr8-db-backups (separate from immigr8-transcripts){db-name}/{YYYY-MM-DD}.dump.gzmanagement/plans/ (one-liner: psql $DATABASE_URL -f restore.sql from a backup)apps/api/src/lib/course-code-validator.ts exposes validateCourseMapping({usEquivalent, courseCode}) returning {ok: boolean, expectedTitle?: string, mismatch?: 'code-not-in-tweds'|'title-mismatches-code'|'title-has-embedded-different-code'}. Uses TWEDS_CATALOG from @immigr8/shared for canonical lookups.POST /api/submissions/:id/mappings and the AI-draft writer both run the validator. On mismatch: log a warning + persist the record but flag it (new column course_mappings.code_alignment_status enum: verified, warning, unchecked).apps/api/src/db/scripts/audit-course-code-alignment.ts scans every existing course_mappings row, emits a CSV at assets/raw/<date>-course-code-misalignments.csv with: submission_id, student_name, grade_level, foreign_course, current us_equivalent, current code, expected title, recommended fix. Dry-run by default; --apply mode applies the recommendations only when high-confidence (code IS in TWEDS, us_equivalent close-match to title via Levenshtein < 3).landing/subprocessors.html if needed (current language is forward-compatible)docs/decisions/2026-05-18_country-rules-source-of-truth.md capturing the architecture decision<UPPER_SLUG>_RULES exported from country-mapping-rules.ts with the same shape as MEXICO_RULESrun-ai-analysis-on-submission.ts on at least one real India student transcript and have Sandy validate the resulting draft<UPPER_SLUG>_RULES exported from country-mapping-rules.ts with the same shape as MEXICO_RULESrun-ai-analysis-on-submission.ts on at least one real Venezuela student transcript and have Sandy validate the resulting draft<UPPER_SLUG>_RULES exported from country-mapping-rules.ts with the same shape as MEXICO_RULESrun-ai-analysis-on-submission.ts on at least one real Vietnam student transcript and have Sandy validate the resulting draft<UPPER_SLUG>_RULES exported from country-mapping-rules.ts with the same shape as MEXICO_RULESrun-ai-analysis-on-submission.ts on at least one real El Salvador student transcript and have Sandy validate the resulting draftassessment_requirednot_required_criteria_met when a corresponding course mapping exists with grade ≥ 70 AND credits ≥ 1 (full credit)NODE_ENV === 'production', the process throws a fatal error if any of: JWT_SECRET is unset / equals 'dev-secret-change-me' / length < 32JWT_SECRET is provisioned separately (not shared with staging via single generateValue: true blueprint reuse)management/runbooks/jwt-secret-rotation.md)apps/api/package.json — drizzle-orm upgraded to ≥ 0.45.2 (latest stable preferred)apps/api/package.json — drizzle-kit upgraded to compatible versionpnpm typecheck clean in apps/api + packages/sharedpnpm test 236/236 still passes in apps/apipnpm audit no longer lists the drizzle-orm HIGH CVEGET /submissions/export accepts optional ?districtId=<uuid> query paramdistrictId is provided, results are filtered to that districtdistrictId is omitted: results are page-capped at 1000 rows + the response includes truncated: true + a hint to use districtId for full exportGET /submissions/export writes a logActivity row with action='exported', metadata={ rowCount, districtIdFilter, scope: 'all' | 'district' }logActivity treatment for PATCH /submissions/bulk (with metadata.changedIds)activity_action enum extended to include 'exported' if not already presentGET /sso-configs requires authenticate + requireRole('platform_admin') middleware/api/sso-configs returns 401 Unauthorizeddistrict_team/district_admin/campus_*/reporting/platform_team request returns 403 Forbiddenplatform_admin request returns the configs list as beforeaction='read', metadata={ entityType: 'sso_configs' }POST /chat/message has a per-user rate limit using express-rate-limit with keyGenerator: (req) => req.user!.userIdCHAT_RATE_LIMIT_MAX + CHAT_RATE_LIMIT_WINDOW_MS){ error, retryAfter } payload + Retry-After headeraction='rate_limited', metadata={ endpoint: 'chat/message' }) so abuse patterns are observableplatform_settings.maxChatTokensPerDay — defer if scope creep, otherwise add a stub columnfile-type (npm package) magic-byte verification after multer parses the upload buffer in validateFile (or a new validateFileMagic helper)validateFile returns descriptive error: "File appears to be <detected> but was uploaded as <claimed>"lib/storage.ts:9 says 128MB, multer config in routes/submissions.ts:27 caps at 50MB. Reconcile to 50MB everywhere (matches IMMIGR8-215 decision)app.use('/uploads', express.static(...)) registration in production OR conditionally register only when NODE_ENV !== 'production'landing/privacy.html (or new dedicated page) covering:legal/dpa-template.docx (or .md if Markdown-friendly) covering:landing/subprocessors.html (or section in landing/privacy.html) listing each sub-processor with:auth.ts:billing.ts:Immigr8 as Insurance — COI 2026.pdfJWT_SECRET on immigr8-api-staging to a freshly-generated 64-char hex value (openssl rand -hex 32)feedback-render-env-vars-safe-pattern.md — never bulk-PUT.Immigr8 as Render env — immigr8-api-staging — pre-rotation 2026-05-02) for \~7 days in case rollback is needed[startup] JWT_SECRET loaded (sha256 prefix: <new-prefix>, env: production) — NO WARN lineopenssl rand -hex 32 value (NOT the same as staging — feedback-render-env-vars-put.md-adjacent precaution)ai_provider varchar(64) column to evaluations table (apps/api/src/db/schema.ts)apps/api/src/db/scripts/apply-immigr8-NNN-evaluations-ai-provider.ts (idempotent ALTER TABLE ... ADD COLUMN IF NOT EXISTS)result.providerName through to the evaluation insert/upsertevaluation.aiProvider and pass to TrainingExamplesRepository.captureFromApprovalai_provider = NULL (acceptable — they predate the adapter pattern)apps/api/src/db/scripts/backfill-training-examples.tsis_ai_draft = false (approved / human-entered) AND there's at least one course_mappingai_provider = null for backfilled rows (we don't know which model produced the original — could be human-entered, IMMIGR8-118 auto-applied, or post-IMMIGR8-257 draft-then-approved)approved_at = evaluations.updatedAt (best approximation)Renewals page in [hq.immigr8.net](<http://hq.immigr8.net>) (sidebar nav alongside Districts, Submissions, etc.). Lists every district whose subscription is approaching renewal (default: next 90 days) in a sortable table: District, plan tier, renewal date, days-until, owner, status, last-touch.Upcoming -> Outreach Sent -> Quote Sent -> In Negotiation -> Renewed / Churned / On Hold. Status transitions logged with timestamp + user.Renewed or On Hold. Fires from existing notification infrastructure.Renewed), drill-down link to the page. Optional second tile: "Renewals YTD - won / churned / pending."?inline=true query → sets Content-Disposition: inline. Client uses window.open(blobUrl, '_blank') when label is "Preview".completed (preserved current behavior — Content-Disposition: attachment by default, a.click() on client).apps/web/public/site.webmanifest "name" field updated from "Immigr8 HQ" to "Immigr8" (or whatever Jake prefers for the customer app label)apps/web/public/site.webmanifest "short_name", "theme_color", "background_color" reviewed + updated to brand colors (currently #0a0a0b which is HQ's dark-mode background)apps/web/public/favicon-192.png + favicon-512.png + favicon.png verified to match the brand-blue circle + white "8" design (regenerate from the SVG if mismatched — use macOS Preview or ImageMagick rsvg-convert)apps/web/public/apple-touch-icon.png verified brand-correct (regenerate if mismatched)hq/public/assets/favicon.svg replaced with a brand-aligned design (either the same brand-blue+white-8 as app, OR a clearly intentional HQ variant per Jake's call — currently it's just an orange dot which is neither)?v=2026-05-12 query string in HTML refs OR add a <meta name="theme-color"> change to force browser revalidation (some users have stale favicons cached from prior visits)app.immigr8.net — confirm home-screen label says "Immigr8" not "Immigr8 HQ" + icon is brand-correcthq.immigr8.net after deploy — confirm HQ favicon is brand-alignedcourse_mappings.display_order INT NULL column (null = use default sort, integer = explicit position within grade-level)PATCH /api/submissions/:id/mappings/reorder accepts { orderedIds: string[] } and writes display_order 0..NGripVertical icon, lucide-react) on each row in GradeMappingTabledisplay_order when set, falls back to subject-category sort otherwisetraining_pairs table with the de-identified shapeformatCourseDisplay(usEquivalent, courseCode, foreignCourse) strips trailing 6-8 char codes (TWEDS digits + PES alphanumerics) from the label, then appends the canonical courseCode. Lives at packages/shared/src/format-course-label.ts, exported from @immigr8/shared.apps/api/src/lib/generate-evaluation-pdf.ts:322 uses the helper so every Course cell shows exactly one PEIMS reference.apps/web/src/pages/SubmissionDetail.tsx uses the helper in both CoursePicker instances (AI-draft row + manual-entry row) AND in the fmtCourseLabel() helper used by other render paths on the page.English II ... - #<one-code>.GET /api/briefs/standup.json endpoint on api-staging with chosen auth approachindex.html becomes a thin shell that fetches + renders client-sidearchive/{date}.html03220200 to 0310060003102400addCompactHeader() renders a 12mm continuation band on pages 2+ (logo + student-name/country + "HS Credit Evaluation (continued)" + blue rule). First page keeps the full cover header for the identifying info.cellPadding tightened (1mm vertical) so rows render \~4.2mm vs prior \~4.6mm.estimateGradeSectionHeight() updated to match real measurements - no more premature page breaks from a stale conservative estimate.nav() helper (data URI; no separate favicon usage)#root, but first paint is real content - no more flash/this-week -> /live.html and /analytics -> /analytics.html. Nav links updated to use the clean paths