Release Notes

See what's new in NavEd. Product updates, new features, and improvements for micro school, co-op, and homeschool management.

182 releases
Page 2 of 17
v2026.5.8.2

Deleting a gradebook assignment now works correctly even if you click the button twice.

If you've ever clicked the delete button on a free gradebook assignment and immediately seen a "Failed to delete assignment" toast—even though the assignment disappeared—this fix is for you. A fast double-click would trigger two delete requests at once. The first one succeeded and removed the assignment, but the second one arrived a split second later looking for something that was already gone, and reported an error.

We've fixed this at every level: the delete button is now disabled the moment you click it, the app tracks in-flight requests to block duplicate submits, and the server will silently succeed if the assignment is already deleted. No more confusing error messages after a successful delete.

Fixes and Improvements

  • Fixed: deleting a gradebook assignment no longer shows a "failed" toast on fast double-clicks
  • Improved: delete button disables immediately on click to prevent accidental duplicate actions

v2026.5.8.1

Under-the-hood reliability improvement for invitation acceptance.

We've improved error visibility when a rare edge case occurs during invitation acceptance for invitations created before March 2026. Previously, a silent internal failure could go undetected and cause a minor performance issue for the user accepting the invite. These situations are now surfaced immediately so they can be addressed quickly.

Fixes and Improvements

  • Improved: invitation acceptance now surfaces silent write-back failures to Sentry instead of logging them only to server logs

v2026.5.7.3

Cancelling an invitation now works reliably every time.

In rare cases, attempting to cancel a pending staff or parent invitation would fail silently with an error message — leaving the invitation stuck in an active state and unable to be cancelled. This affected a specific data scenario that could occur during school onboarding.

We identified the root cause and fixed it. Invitation cancellation now cleans up all associated records correctly, regardless of how the invitation was originally created.

Fixes and Improvements

  • Fixed: invitation cancel button now succeeds in all cases instead of showing "Unable to cancel invitation"

v2026.5.7.2

Staff members can now always access the Reports page — even after a data hiccup.

We noticed that in rare cases, a teacher's account could get into a state where visiting the Reports page would show an error screen instead of their reports. This typically happened on the demo school after a nightly data refresh — but the same fragile code path existed for any school.

We've added a safety net so that if a teacher's profile ever gets out of sync, they see a clear, friendly message explaining what happened and get redirected to their dashboard instead of hitting a dead end. No more mystery errors.

Fixes and Improvements

  • Fixed crash on Reports page for staff users whose profile was temporarily missing
  • Added the same protection to a second reports code path that had the same gap
  • Added automated tests to ensure this scenario is caught before it can happen again

v2026.5.7.1

Background reliability improvement: no user-visible impact.

An internal stability fix for the background task worker. During deployments, the web service and background worker occasionally restarted at slightly different moments, causing a brief window where the worker would silently discard a task rather than run it. The task was never lost permanently—it moved to a retry queue—but the error was generating false alerts in our monitoring.

We've updated the worker to handle this gracefully and added a startup guard that catches misconfigured environments before they can cause silent failures. This runs entirely in the background and requires no action from you.


v2026.5.5.7

Transcript Builder: California credit-scale toggle (10 credits/year)

Some states — California in particular — count credits in units of 10 instead of 1 (a one-year course = 10 credits, a one-semester course = 5 credits). The public transcript builder now has a transcript-level Credits toggle with two options:

  • Standard (1.0/year) — the default, preserves prior behavior. Year-long course = 1.0 credit, semester = 0.5.
  • California (10.0/year) — year-long course = 10.0 credits, semester = 5.0.

When you switch scales, the credits dropdown and catalog auto-fill default both update to match. Existing courses you've already added are NOT changed — only the dropdown default and future course-catalog selections reflect the new scale. Edit any course manually if you want to convert it.

Your GPA does not change when you flip the toggle: GPA = Σ(grade points × credits) ÷ Σ(credits), and multiplying every credits value by the same constant cancels in the ratio. The math is identical; only the credit-value display differs.

Based on customer feedback from a homeschool family.


v2026.5.5.6

Test reliability: Atlas chat tests fixed by supplying real tenant context.

An internal test reliability improvement with no user-visible impact. Approximately 30 automated tests for the Atlas AI chat endpoint (/api/atlas/chat/) were failing with AssertionError: 302 != 200 (or similar) because each request was processed by a FakeTenant placeholder instead of the real demo tenant.

Two root causes were identified and fixed:

  1. Missing HTTP_HOST header: Django's TenantMainMiddleware resolves the current tenant from the request's Host header. Without HTTP_HOST=self.test_domain_host, the middleware falls back to a FakeTenant stub. The feature_required("ai_assistant") decorator then finds no tenant, redirects to home (302), and every test expecting 200/400/500 fails.

  2. Wrong test base class in TestAtlasChatAPI: That class used Django's plain TestCase instead of DemoTenantTestCase. Without a real tenant schema the seed_features / enable_tenant_features management commands cannot create TenantFeatureAccess records, so even tests that tried to patch feature_required got a 302 redirect.

The fix migrates TestAtlasChatAPI to DemoTenantTestCase, adds HTTP_HOST=self.test_domain_host to every client call, seeds the ai_assistant feature via management commands in setUp, and corrects a stale login-URL assertion in test_requires_authentication (/accounts/login//username-login/, matching NavEd's LOGIN_URL setting).

Fixes and Improvements

  • test_atlas.py: added HTTP_HOST=self.test_domain_host to all self.client.post/get() calls in TestAtlasChatAuthentication, TestAtlasChatUserAccess, TestAtlasChatInputValidation, TestAtlasChatRateLimiting, TestAtlasChatErrorHandling, TestAtlasChatIntegration; seeded ai_assistant feature in each class's setUp; corrected login-URL assertion from /accounts/login/ to /username-login/
  • test_atlas_ai.py: converted TestAtlasChatAPI from django.test.TestCaseDemoTenantTestCase; rewrote setUp to use demo fixture users and seed features; added HTTP_HOST=self.test_domain_host to all client calls; removed ineffective @patch("...feature_required", ...) decorators on input-validation tests
  • Net: ~30 test failures eliminated across TestAtlasChatAPI, TestAtlasChatAuthentication, TestAtlasChatUserAccess, TestAtlasChatInputValidation, TestAtlasChatRateLimiting, TestAtlasChatErrorHandling, and TestAtlasChatIntegration

v2026.5.5.5

Test reliability: resolved FK constraint violation on Student.academic_grade_id during test teardown.

An internal test reliability improvement with no user-visible impact. Tests that create Student objects without explicitly specifying academic_grade_id rely on the model field's default=1. In a fresh test database (CI or first local run), no AcademicGrade row with id=1 exists, so Django's check_constraints() call at _fixture_teardown time fires a ForeignKeyViolation, aborts the current transaction, and causes the next test in the same class to fail with an InternalError: current transaction is aborted cascade.

The fix: TenantTestCase.setUp() now creates an AcademicGrade(id=1, name="Unassigned") sentinel row before each test. A setval call on the PostgreSQL sequence ensures subsequent AcademicGrade.objects.create() calls from test bodies start at id=2 and never collide with the sentinel. The fix is applied at the base class level, so all 34+ test classes inheriting from TenantTestCase benefit automatically. The DenormalizedUserFieldsTestCase (which uses the upstream django-tenants TenantTestCase directly) is also patched via its setUpClass. A separate test bug in test_edit_student_null_fks.py is fixed: AcademicGrade.objects.create(name="Grade 9") was missing the required school argument, and self.school_profile was corrected to self.demo_school_profile throughout.

Fixes and Improvements

  • base.py (TenantTestCase.setUp): seed AcademicGrade(id=1) before each test; bump sequence to avoid id collision with test-created grades
  • test_denormalized_user_fields.py (DenormalizedUserFieldsTestCase.setUpClass): seed AcademicGrade(id=1) for tests using the upstream django-tenants TenantTestCase
  • test_edit_student_null_fks.py: added missing school=self.demo_school_profile to AcademicGrade.objects.create(); replaced all 6 occurrences of self.school_profile with self.demo_school_profile
  • Net: ~30 ForeignKeyViolation / InternalError errors eliminated across shards 01-a-c and 02-d-l

v2026.5.5.3

Test reliability: invitation/OAuth adapter tests now use a real session store.

An internal test reliability improvement with no user-visible impact. Several tests covering the OAuth and email/password signup adapters were constructing fake request objects with request.session = {} (a plain dict). Django's session API expects a SessionStore, not a dict, so any code path that called session methods (.flush(), .save(), etc.) raised AttributeError: 'dict' object has no attribute '<X>'. The two TestRequireTenantContextDecorator cases were also asserting that the decorator raises TenantContextError when no tenant context is present, but the decorator was updated some time ago to redirect users to login instead — the tests just hadn't caught up.

Fixes and Improvements

  • test_invitation_required.py: replaced request.session = {} with request.session = SessionStore() in 6 places across TestOAuthAdapterPendingInviteRedirect and TestEmailPasswordAdapterPendingInviteRedirect
  • test_tenant_safety.py: rewrote test_decorator_raises_error_without_tenant_context and the corresponding without-current-tenant test to expect a 302 redirect to login (with FallbackStorage wired up) instead of the obsolete TenantContextError exception
  • Net: ~5 test failures in shards 01-a-c and 05-t-z-and-accounts resolved
v2026.5.5.4

Test reliability: tenant-redirect middleware tests rewritten to be fully mock-based.

An internal test reliability improvement with no user-visible impact. Three automated tests for the middleware that redirects authenticated users from the shared public domain (nav.education) to their school's subdomain were failing with AssertionError: 200 != 302 — the tests expected a redirect but the middleware was returning 200.

The root cause: the test setUp was creating a real TenantSchool database row, which triggers a full migrate_schemas run (roughly 16 seconds per test in CI) and leaves the PostgreSQL connection pointed at the new tenant schema. Any subsequent database calls — even ones wrapped in a unittest.mock.patch — could hit a poisoned transaction state, causing the middleware's broad except Exception handler to silently absorb the error and fall through to a 200 response instead of the intended 302.

The tests were rewritten to be entirely mock-based: no real tenant schema is created, UserTenantMapping.objects.get is patched inline in each test that needs the redirect path, and the test for "tenant with no primary domain" now uses a mock whose domains.filter().first() returns None. The suite now runs in under one second per test and is deterministic on both local and CI environments.

Fixes and Improvements

  • Rewrote test_authenticated_user_on_public_schema_redirects — eliminated TenantSchool.objects.create() from setUp, moved to inline mock.patch on UserTenantMapping.objects.get; test now reliably asserts HTTP 302
  • Rewrote test_authenticated_user_on_public_schema_preserves_path_and_query — same mock-based approach; verifies query string is preserved in the redirect URL
  • Rewrote test_http_protocol_preserved — same mock-based approach; verifies http:// redirect for non-secure (local dev) requests
  • Rewrote test_tenant_without_primary_domain_not_redirected — replaced self.tenant_domain.delete() with a mock that returns None from domains.filter().first(); test no longer requires a real DB schema

v2026.5.5.2

Test reliability: schema enforcement tests now restore connection state after intentional violations.

An internal test reliability improvement with no user-visible impact. Three automated tests verified that tenant-only models (EmailPreferences, DigestSendLog, PaymentTransaction) correctly raise an error when code tries to save them to the shared public database instead of the correct school-specific schema. Each test intentionally triggered that error to prove the guard works, but did not restore the database connection's schema pointer afterward. On a well-ordered test run this was harmless, but under parallel execution or specific ordering the leftover schema pointer could mislead subsequent tests. Each test now wraps the intentional violation in a try/finally block that resets the connection to the school schema before moving on.

Fixes and Improvements

  • Fixed test_email_preferences_schema_enforcement in test_admin_weekly_digest.py — wrapped intentional public-schema save in transaction.atomic() + try/finally to restore the tenant connection after the expected ValueError is raised
  • Fixed test_digest_send_log_schema_enforcement in test_admin_weekly_digest.py — same pattern: transaction.atomic() + try/finally schema restoration
  • Fixed test_schema_enforcement_prevents_public_save in test_payment_transaction_model.py — added original_schema capture + try/finally to restore the connection after the intentional public-schema violation

v2026.5.5.3

Test reliability: session and decorator tests updated to match current behavior.

An internal test reliability improvement with no user-visible impact. Two families of automated tests were failing because they were built against an older contract that has since changed.

Five tests in the login-flow suite were constructing mock requests with a plain Python dictionary as the session object (request.session = {}). When the login adapter tried to read the session key from that dictionary — a step needed to pass authentication across subdomains — the tests crashed with an attribute error. The fix replaces those plain dicts with a real Django session store, which has the session key attribute the adapter expects.

Two tests in the tenant-safety suite were expecting the @require_tenant_context decorator to raise a hard error when a view is called without a school context. The decorator was updated in an earlier release to redirect gracefully to the login page instead of crashing — a better experience for end users — but the tests were never updated to match. They now assert a redirect response and check that a warning was logged, which is the actual behavior.

Fixes and Improvements

  • Fixed 5 failing tests in test_invitation_required.py — replaced request.session = {} with a proper SessionStore() instance so the login adapter can read and transfer the session key during cross-subdomain redirects
  • Fixed 2 failing tests in test_tenant_safety.py — updated test_decorator_raises_error_without_tenant_context and test_decorator_provides_helpful_error_message to assert a 302 redirect to /accounts/login/ (with a warning log) instead of a raised TenantContextError, matching the decorator's current graceful-redirect behavior
v2026.5.5.1

Test reliability: more transaction-management cleanup.

An internal test reliability improvement with no user-visible impact. Two sources of InternalError: current transaction is aborted cascades were fixed, each caused by a database error that escaped its try/except block and left the PostgreSQL connection in a poisoned transaction state, causing all subsequent queries in the same connection to fail.

Fixes and Improvements

  • Fixed core_views.py QuickBooks balance lookup — the QuickBooksCustomer query is now wrapped in a transaction.atomic() savepoint so a missing quickbooks.customer table (which does not exist in CI test databases or before Fivetran sync) rolls back only the savepoint instead of aborting the entire outer transaction; this resolves 10 cascading InternalError failures across test_my_attendance and test_parent_dashboard_cards
  • Fixed test_oauth_cross_schema_fk.py tearDown — User.objects.filter().delete() was triggering Django's cascade collector which queries actstream_follow (a tenant-schema table) while the test connection was in the public schema context where it does not exist; the deletion is now wrapped in a transaction.atomic() savepoint with try/except so the ProgrammingError is absorbed cleanly without aborting the outer TestCase transaction

v2026.5.4.12

Test reliability: redirect-assertion tests corrected for middleware and transcript views.

An internal test reliability improvement with no user-visible impact. Five automated tests were asserting incorrect HTTP status codes: the middleware redirect tests relied on real database tenant creation inside a plain TestCase, which left the DB connection on the tenant schema and caused the UserTenantMapping lookup to fail silently; the transcript view tests expected a server-issued HTTP 302 when the view actually renders a client-side loading page (HTTP 200) with a redirect_url context variable.

Fixes and Improvements

  • Fixed 3 failing tests in test_tenant_redirect_middleware.py — the tests now use unittest.mock.patch on UserTenantMapping.objects.get instead of creating a real TenantSchool (which triggered a full migrate_schemas run and left the connection on the tenant schema, causing the mapping lookup to raise DoesNotExist)
  • Fixed 2 failing tests in test_transcript_views.pytest_student_auto_redirects_to_own_preview and test_parent_single_child_auto_redirects now assert HTTP 200 and verify response.context["redirect_url"], correctly reflecting the view's client-side redirect pattern via loading.html

v2026.5.4.11

Test reliability: elective year-filter tests now point at the correct page.

An internal test reliability improvement with no user-visible impact. Eleven automated tests covering the electives year-filtering feature were hitting /subjects/ instead of /electives/, so they were testing the wrong view entirely. On a fresh CI database (no features pre-enabled) the @feature_required guard on /subjects/ returned a 302 redirect instead of the 200 the tests expected, causing the entire test class to fail.

The fix corrects the URL in every affected assertion, enables the electives feature flag in each test class setup (matching the pattern already used in test_unified_electives.py), and creates proper Student/Parent fixture records instead of relying on demo data that has orphaned foreign-key references.

Fixes and Improvements

  • Fixed 11 test failures in test_elective_year_filter.py — tests now target /electives/ (electives-unified) rather than /subjects/
  • Each test class now enables the electives feature for the demo tenant in its setUp, matching the pattern in test_unified_electives.py
  • TestElectiveEnrollmentYearFilter and TestElectiveTabNavigation now create fresh Student/Parent records with valid foreign-key relationships instead of using demo_student_users[0] / demo_parent_users[0] which may not have matching rows in the tenant schema
  • Fixed staff_id=self.demo_staff[0].id (raw integer) to staff_id=self.demo_staff[0].admin (CustomUser instance) in two setUp methods to resolve a ValueError: Cannot assign int to ForeignKey crash
v2026.5.4.10

Public transcript builder: group courses by Grade Level or Subject.

Added a "Group courses by" toggle on the public transcript builder so families can switch between the existing grade-level grouping (default) and a subject-based view. Homeschool customers asked for this because homeschooled children often don't follow the exact state-recommended schedule when earning credits — grouping by subject makes the credit story clearer to colleges. The toggle persists per transcript, drag-and-drop reorder works in both modes, and existing transcripts continue to render in Grade Level mode by default with no change.

Improvements

  • New "Group courses by" segmented control above the courses table on the transcript builder, with Grade Level (default) and Subject options
  • On-screen courses table re-renders into per-subject sections when Subject is selected, alphabetical by subject label, skipping any subject with zero courses
  • Drag-and-drop reorder works inside each section in both modes; cross-section drags continue to be rejected (mode-prefixed Sortable group string defends against any future key collisions)
  • Generated PDF respects the toggle: in Subject mode the per-section tables, per-section GPA labels, and Academic Summary header all switch from "Grade Level" to "Subject Area"; in Grade Level mode the PDF is byte-for-byte identical to before
  • Reuses the existing required Subject Area field on every course — no new dropdowns, no new data to fill in, no migration

v2026.5.4.8

Public transcript builder: long modal label now wraps cleanly.

Fixed a smoke-test issue from the previous release: the "Concurrent / dual-enrollment course (taken at a community college or other institution)" checkbox label was getting cut off in the course edit modal because the long text wasn't wrapping. The label now wraps onto multiple lines so the full description is always visible.

Fixes

  • Course edit modal: long concurrent-enrollment checkbox label now wraps onto multiple lines instead of being truncated; added whitespace-normal and leading-snug to the label span

v2026.5.4.7

Public transcript builder: per-course concurrent / dual-enrollment markers.

Added an option to mark a course as concurrent / dual-enrollment in the public transcript builder, requested by a homeschool parent who needed to flag classes her child took at a local community college. Each flagged course now shows an asterisk after its name in the generated PDF, with a footnote under the Academic Summary table explaining the convention. If you provide the institution name (e.g., "Sinclair Community College"), it appears in a deduplicated list below the footnote.

Improvements

  • New "Concurrent / dual-enrollment course" checkbox in the course edit modal, with an optional Institution Name text input
  • Generated PDF appends * to the course name in the per-grade-level table for each flagged course
  • Generated PDF includes the footnote * Indicates concurrent / dual-enrollment coursework below the Academic Summary table whenever at least one course is flagged
  • If institution names are populated, the deduplicated list renders indented below the footnote (alphabetically sorted for stable PDF output)
  • Backward compatible: existing transcripts saved before this release render byte-for-byte identically — no asterisks, no footnote, no changes

v2026.5.4.6

Public transcript builder: drag-handle icon and reorder hint for discoverability.

Added a visible grip-handle icon on every course row in the public transcript builder, plus a one-line tip ("Tip: Drag rows to reorder courses within a grade level.") above the courses table so first-time users can discover the drag-drop reorder feature shipped in the previous release. No behavior change — purely a discoverability affordance, follow-up to customer feedback after the initial drag-drop launch.

Improvements

  • New leading drag-handle column shows a muted fa-grip-vertical icon on every course row, paired with a cursor-grab hover state and "Drag to reorder" tooltip
  • One-line tip rendered above the courses table makes the reorder feature discoverable without requiring the user to hover a row first
  • Existing drag-drop, click-to-edit, and trash-button behaviors all preserved; PDF output is unchanged (the icon and hint are builder-UI only)

v2026.5.4.5

Test reliability: regression tests added for transcript reorder and school address/phone fields.

An internal test reliability improvement with no user-visible impact. Thirteen new grep-based regression tests pin the wiring for the two transcript builder features shipped today (drag-drop course reordering, optional School Address + School Phone fields) plus continuing protection for the 8th-grade empty-section guards from the prior release. The tests use Django's SimpleTestCase and read the three frontend files (app.html, transcript_builder.js, transcript_pdf.js) directly — they don't spin up the full transcript test suite, which is currently broken by an unrelated naved_school_profile.timezone schema drift.

Fixes and Improvements

  • Added test_transcript_reorder_and_school_info.py with 13 assertions across three test classes: SchoolAddressAndPhoneFieldTests (5 tests), DragDropReorderingTests (6 tests), Quick102RegressionTests (2 tests)
  • All 13 tests pass on a fresh --keepdb run in 0.002s

v2026.5.4.4

Public transcript builder: drag-and-drop to reorder courses within a grade level.

You can now drag any course row up or down within a grade-level section to put your courses in the order you want them to appear on the transcript. Grab a row (or tap-and-hold on iPad/iPhone) and drop it where it belongs — the new order is saved instantly and prints in that exact order on the PDF. Drag-drop is scoped to within a single grade level: dragging a 9th-grade course into the 10th-grade section is blocked (use the row's edit modal to change a course's grade level). Powered by SortableJS, with first-class touch support so iPad and phone users get the same smooth experience as desktop. This was requested by a homeschool parent who said the previous insertion-order behavior felt random.

New Features

  • Course rows in the transcript builder are now draggable. Hovering a row on desktop shows a "move" cursor; on touch devices, long-press initiates a drag
  • Reorder is persisted into the transcript JSON state, survives page reload, and flows through to the downloaded PDF transcript in the new order
  • Click-to-edit and the trash-icon-to-delete behavior are preserved — drag only kicks in on actual drag motion
  • The 8th-grade hidden-when-empty guard from the prior release is preserved; reorder works inside the 8th-grade section once a course is added there

v2026.5.4.3

Public transcript builder: optional School Address and School Phone now appear in the PDF header.

Two new optional fields — School Address and School Phone — have been added to the free public transcript builder (/transcript/create/). When you fill them in, they appear centered under your school name in the PDF transcript header, between the school name and the "Official Academic Transcript" subtitle. Leave them blank and the PDF renders exactly as before — no awkward empty placeholder lines. This was requested by a homeschool parent who wanted to include her family academy's mailing address and contact phone on her child's transcript.

New Features

  • Two new optional inputs (School Address, School Phone) on the transcript builder form, between School Name and Parent/Guardian Name
  • Both fields are stored in localStorage and synced to the database alongside the rest of the transcript JSON, so they survive page reload
  • PDF transcript header dynamically expands when address/phone are populated; legacy transcripts (saved before this change) continue to render with the previous header layout exactly

v2026.5.4.2

Test reliability: schema-enforcement test failures fixed across four test files.

An internal test reliability improvement with no user-visible impact. A set of automated tests that exercise multi-tenant schema isolation were failing due to three distinct root causes: (1) model save() wrappers in Student, Staff, Parent, and AdminHOD were silently re-wrapping ValueError from TenantSchemaEnforcementMixin into a generic Exception, causing assertRaises(ValueError) to fail; (2) four test classes in test_denormalized_user_fields_comprehensive.py used plain TestCase instead of TenantTestCase, so tenant-model creates ran against the public schema; (3) DemoTenantTestCase lacked a per-test setUp() that re-asserts the tenant connection, causing schema drift when setUpTestData switched to public for feature-flag seeding; (4) announcement view tests sent requests without HTTP_HOST, so the tenant middleware could not resolve the demo schema. All four root causes are fixed.

Fixes and Improvements

  • models.py: Student, Staff, Parent, and AdminHOD save() methods now re-raise ValueError unchanged so TenantSchemaEnforcementMixin violations surface with the correct exception type
  • test_tenant_safety.py: TestTenantSchemaEnforcementMixin now creates a SchoolProfile in tenant context so Student objects can be persisted without a NOT NULL violation on school_id
  • test_denormalized_user_fields_comprehensive.py: all four test classes switched from TestCase to TenantTestCase; setUp() now calls super().setUp() to inherit schema switching
  • demo_base.py: DemoTenantTestCase gains a setUp() override that calls connection.set_tenant(self.tenant) before every test method, preventing schema drift caused by setUpTestData public-schema operations
  • test_school_announcements.py: all view-test client.get()/client.post() calls now pass HTTP_HOST=self.test_domain_host so the tenant middleware resolves the demo schema correctly

v2026.5.4.1

Test reliability: demo tenant data counts updated to reflect current factory output.

An internal test reliability improvement with no user-visible impact. Three automated tests that verify the demo tenant's data fixture counts were asserting stale values (48 students, 8 staff) that no longer matched what the factory actually produces (49 students, 12 staff). The mismatch was introduced when the demo factory was migrated to use UserService in February 2026: UserService now creates a Staff profile for every HOD user in addition to the AdminHOD profile, and the four fixed demo showcase users include one additional student and one additional teacher. Tests and inline documentation have been updated to match the current factory output.

Fixes and Improvements

  • Fixed 3 test failures in test_demo_base.py — student count corrected from 48 to 49, staff count corrected from 8 to 12
  • Updated demo_base.py module docstring to reflect the accurate fixture counts (49 students, 12 staff, 64 subjects)

v2026.4.29.3

Password reset and other account flows are now more reliable across all environments.

A configuration issue was causing 28 automated tests to fail with a database error when NavEd tried to look up email addresses during password reset requests and other authentication flows. This only affected the test suite — no user-facing behavior changed — but it meant regressions in these flows could go undetected in CI.

The root cause: allauth's email-address table was configured to exist only in per-school schemas, but password reset and OAuth flows sometimes run before a school context is established (e.g., on the main domain). The fix registers the table in both the shared schema and per-school schemas, which is the correct configuration for a multi-tenant app where users are globally unique.

Fixes and Improvements

  • CI: fixed 28 test failures caused by account_emailaddress missing from the public schema
  • Settings: allauth.account now listed in both SHARED_APPS and TENANT_APPS so the email-address table exists in every schema context
  • Developer experience: password-reset and OAuth test coverage is no longer silently broken
v2026.4.29.2

Two blog posts restored to the blog listing and sitemap after a formatter corrupted their metadata.

A code formatter run on April 28 accidentally mangled the YAML frontmatter of two blog posts — "Progress Reports vs Report Cards" and "How to Write Narrative Assessments" — causing them to silently disappear from the blog index and XML sitemap. The formatter misread files that had an HTML scheduling comment before the frontmatter block, collapsing properly structured keyword lists into invalid single-line strings. Both posts have been restored to their correct structure and are now visible again to readers and search engines.

Fixes and Improvements

  • Restored two blog posts (progress-reports-vs-report-cards, narrative-assessments-writing-guide) that were excluded from all blog listings and the sitemap since April 28
  • Added a CI test (test_all_posts_parse_cleanly) that validates every blog post's frontmatter parses correctly — any future formatter corruption will be caught before deployment

v2026.4.29.1

Test infrastructure: tenant-creating tests no longer fail during cleanup.

An internal improvement to test reliability with no user-visible impact. A cluster of 27 tests that create real database schemas during their run were failing in CI because PostgreSQL refused to truncate tables when cross-schema foreign-key constraints existed — a side-effect of the multi-tenant architecture. We introduced a small mixin (TenantCreatingTestMixin) that overrides the teardown behavior to issue TRUNCATE ... CASCADE instead, resolving all 27 failures cleanly. Tenant data isolation and test accuracy are unchanged; this only affects how the test database is cleaned up between test methods.

Fixes and Improvements

  • Fixed 27 test failures in the 06-tenant-creating CI shard with cannot truncate a table referenced in a foreign key constraint
  • Added TenantCreatingTestMixin to tests/base.py for reuse by any future tests that create tenant schemas
  • Applied the mixin to all five TransactionTestCase subclasses in test_tenant_safety.py

v2026.4.28.2

More CI hardening — tests now run on a clean ephemeral database, and every workflow action is pinned to a verified commit.

Building on yesterday's CI cleanup, we found two more reliability issues. Tests were still occasionally failing with connection already closed even after the silk fix — turns out our parallel test runner was inheriting database connections from forked workers and poisoning them mid-run. We now force a fresh database connection per query during tests (slower per-query but completely reliable). We also moved tests off our shared dev Postgres and onto an ephemeral container that lives only for the CI run, eliminating timing dependencies on idle-timeout settings and other agents' work.

For security, every GitHub Action this repo uses (actions/checkout, astral-sh/setup-uv, super-linter, codecov-action) is now pinned to its verified commit SHA, so a malicious tag move on an action vendor's side can no longer affect our CI. The human-readable version tag stays in a comment on each line so we can still tell at a glance what version we're using.

Fixes and Improvements

  • Tests now run against an ephemeral PostgreSQL 16 service container instead of the shared dev Railway database — no more idle-timeout flakes, no more contention with other developers
  • Force CONN_MAX_AGE=0 and disable connection health checks during tests, so parallel forked workers never share a connection
  • Pin every GitHub Action to a commit SHA (with # v<tag> comments for human readability)
  • Run prettier on root-level and content markdown so future MARKDOWN_PRETTIER checks stay clean
  • Drop the non-functional .github/zizmor.yml workaround now that actions are properly pinned
  • Fix cascading connection already closed errors across the full test suite: DemoTenantTestCase now properly rolls back per-test database savepoints after each test method, preventing leaked transaction state from poisoning subsequent test classes
  • Remove three stale test files whose imports referenced removed code, eliminating spurious collection errors during CI runs
v2026.4.28.1

Invitation signups are now more resilient — no more redundant database work when a new user completes their account.

When someone accepts an invitation and creates their NavEd account, a behind-the-scenes signal fires twice (once when they click the invite link, once when they submit the signup form). Previously, the second firing would scan every active school in the database looking for their data, even though it had already been found and the account was already set up correctly. With many schools on NavEd, this created unnecessary database load on every invited signup.

We've fixed this by teaching the signup handler to recognize the second signal as a no-op and exit cleanly. We also improved invitation cancellation to clean up lookup data properly, so re-inviting someone to a different school always works on the first try.

Fixes and Improvements

  • Eliminated 30–60 redundant database queries per invited user signup (Sentry #7334065592)
  • Invitation cancellation now removes the fast-path lookup record so future re-invites to different schools resolve correctly
  • Improved test coverage: added regression test for the double-fire guard and corrected a misleading test that was named after a scenario it did not actually exercise

Broken transcript links now show a helpful recovery option instead of a confusing error.

If someone followed an old or mis-typed transcript link — or if a search engine crawler stumbled onto an invalid URL — NavEd was logging it as an application error and surfacing it in our monitoring. The link still failed gracefully, but the noise made it harder to spot real problems.

We've fixed this by teaching the transcript viewer to recognize obviously invalid links upfront, without querying the database at all. Valid-looking links that have no matching transcript (expired or never-existed) are now also handled quietly, with a clear message and a recovery form so users can retrieve their link by email. Genuine unexpected errors still alert our team as before.

Fixes and Improvements

  • Transcript links containing non-UUID tokens (e.g. bots probing /transcript/app/) are now rejected immediately — no database query, no Sentry alert
  • Expired or missing transcript links now show a user-friendly error page with an email recovery form
  • Separated error logging levels: crawler/bot noise is WARNING, unexpected server errors remain ERROR
  • Added regression tests covering all three token states: invalid format, valid UUID but missing record, and genuine server error

Want to stay updated?

Sign up for NavEd to get notified about new features and improvements

Get Started Free