Skip to content

Serve djangobuilder5 at / and move djangobuilder.io to /legacy/ - #267

Open
mmcardle wants to merge 25 commits into
masterfrom
feat/db5-root-cutover
Open

mmcardle wants to merge 25 commits into
masterfrom
feat/db5-root-cutover

Conversation

@mmcardle

@mmcardle mmcardle commented Sep 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • Layout flipped. djangobuilder5 builds for /, djangobuilder.io for /legacy/, djangobuilder4 stays at /db4/. firebase.json rewrites /legacy/** and /db4/** to their own index.html, everything else to the root index, and 301s /db5 and /db5/** to /.
  • One deploy pipeline. New script/assemble_site.sh is the only place that knows the layout. script/deploy.sh and make deploy use it; new make serve_site serves the assembled site on the Firebase hosting emulator. The stale packages/djangobuilder.io/firebase.json is removed.
  • Old URLs keep working. src/lib/legacyHash.ts in djangobuilder5 rewrites legacy hash URLs at startup (/#/project/<id>/project/<id>, /#/login//login, /#/action?…/action?…, /#/verify/<code> and /#/reset/<code>/action?mode=…, unknown → /). Covers old bookmarks, in-flight verification emails and either form of the Firebase console action URL.
  • Old data keeps working. Projects created before the Dec-2024 core refactor store django.db.models.DateTimeField, django.db.models.ForeignKey and targets like django.contrib.auth.models.User; djangobuilder5 looked those up verbatim and the dashboard threw Unknown field type: django.db.models.DateTimeField (seen on staging). The Firestore mapper now normalises them to registry keys (as the legacy app and db4 already did on read). Five historical field types no longer exist in core at all (AutoField, BigAutoField, CommaSeparatedIntegerField, IPAddressField, NullBooleanField), so the core adapter now skips an unknown field or relationship type with a console warning instead of throwing, and the model editor shows such a field's stored type as <type> (unsupported) instead of silently displaying the first option.
  • Data audit. bun run audit_legacy_data <development|staging|production> is a read-only, quota-friendly Firestore audit (count aggregations + targeted queries, a few dozen reads regardless of size) that reports pre-2024 dotted names, fields using the five retired types, projects below Django 3, and every affected project with owner uid and reason. --full opts into the one-read-per-document integrity scan with its cost printed first. Pure classifier + query builders with unit tests; writes legacy-data-audit.<env>.json.
  • Cross-links. djangobuilder5's top nav has an always-visible Legacy UI link to /legacy/. The legacy app gets a dismissible LegacyBanner linking to / (replacing the end-of-life alert), a base-aware email continue URL (emailActionContinueUrl), relative manifest icons, and the tab title "Django Builder (Legacy)".
  • CI assembles the site on every PR, asserts the three index.html files carry the right asset bases, and uploads one site_dist artifact.
  • Docs. New docs/deployment.md with the environment URLs (dev.djangobuilder.io, staging.djangobuilder.io, djangobuilder.io), layout, local serve, deploy and the manual cut-over checklist. README, AGENTS and the package docs now cover djangobuilder5. Spec and plan under docs/superpowers/.

Verified

  • bun run lint, bun run test (174 core / 9 io / 220 db5), bun run build, db5 type-check: all green.
  • Deployed to development and checked on https://dev.djangobuilder.io/: /db5*/ 301, /legacy/legacy/ 301, deep links under /, /legacy/ and /db4/ resolve to the right app, legacy favicon/manifest/icons 200, both tab titles, banner href /, nav "Legacy UI" href /legacy/.
  • Live end-to-end on the same domain: anonymous sign-in → create project → /#/project/<id> rewritten in place to /project/<id> with the builder open → /legacy/#/project/<id> opens the same project in the legacy app through the shared session.
  • Downloaded a project from the deployed app and ran it: Django 6.0 + Postgres, migrate, check, its 4 generated tests, pages 200, a Post created via the DRF API and via the HTML form.
  • Legacy data: seeded a project on dev in the pre-2024 storage format (dotted field/relationship types, django.contrib.auth.models.User target, a retired CommaSeparatedIntegerField). Pre-fix build reproduced the exact staging error and blanked the page; fixed build renders the dashboard card, generates models.py with DateTimeField, CharField(max_length=30) and ForeignKey("auth.User", …), skips the retired field with one warning, and the editor labels it CommaSeparatedIntegerField (unsupported).

Not in this PR (cut-over checklist in docs/deployment.md)

  • Staging and production deploys.
  • Firebase console → Authentication → Templates → action URL → https://djangobuilder.io/action (the shim keeps the old value working meanwhile).

Test plan

  • CI green (build + assemble/layout check, lint, core, io, db5 tests, db5 type-check)
  • make serve_site/, /legacy/, /db4/ load; /db5/x redirects to /
  • bun run audit_legacy_data production → review affected projects before the production deploy
  • make deploy name=staging → sign in on https://staging.djangobuilder.io/projects with an account that has pre-2024 projects; dashboard and builder load
  • Console action URL, then make deploy name=production → repeat on https://djangobuilder.io/

🤖 Generated with Claude Code

https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge

mmcardle and others added 16 commits September 20, 2026 10:44
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PWYAJWwUpthUkscWT9t3oz
Drop --base=/db5/ from the djangobuilder5 build, build djangobuilder.io with
--base=/legacy/, and rewrite the hosting config: /legacy/** and /db4/** go to
their own index.html, everything else to the root (djangobuilder5) index, and
/db5 plus /db5/** redirect to /. The subfolder rewrites now carry a leading
slash; without it the emulator never matched them, so deep links under /db4/
fell through to the root app. Remove the stale firebase.json copy in the .io
package.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PWYAJWwUpthUkscWT9t3oz
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
…ns, (Legacy) title

The banner replaces the earlier 'may be end-of-lifed soon' alert in the same
slot, since a new version at / supersedes that message.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
…082 and web.app

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
…ff the key's referrer list

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
@mmcardle
mmcardle marked this pull request as ready for review September 20, 2026 11:04
mmcardle and others added 9 commits September 20, 2026 18:16
…estore

Projects created before the Dec-2024 core refactor store field and
relationship types as full Django paths (django.db.models.DateTimeField,
django.db.models.ForeignKey) and relationship targets as full class paths
(django.contrib.auth.models.User). The core registries are keyed by bare
class name, so FieldTypes[type] came back undefined and the dashboard threw
'Unknown field type: django.db.models.DateTimeField' for any such project.
The legacy app and db4 both normalise on read; now the mapper does too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
…d of throwing

Five historical field types (AutoField, BigAutoField,
CommaSeparatedIntegerField, IPAddressField, NullBooleanField) no longer exist
in the core registry, so one such field in one project would still white-screen
the dashboard after name normalisation. Mirror the existing unresolved-target
policy: warn and skip that field or relationship, keep rendering the rest.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
A select whose value matches no option renders the first option, so a
legacy CommaSeparatedIntegerField field displayed as ArrayField while being
skipped from generation. List the stored type as '<type> (unsupported)' until
a current type is chosen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
bun run audit_legacy_data <development|staging|production> reads the five
collections through the Firestore REST API as the project owner (using the
Firebase CLI login, or application-default credentials when present), and
reports: dotted pre-2024 type names (normalised on read), field/relationship
types the core no longer knows, unresolved targets and parents, the
django_version distribution incl. pre-Django-3 projects, dangling refs and
orphans, and every project whose generated code or displayed version would
differ from what its owner built, with reasons. Writes a JSON report at the
repo root. Nothing is written to Firestore. Classification is pure and
unit-tested; the CLI only fetches and prints.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
…onment argument

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
indexOf('--out') is -1 without the flag, so the filter 'i !== outFlag + 1'
discarded argv[0]. Extract parseAuditArgs with tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
…d --full

Listing every document costs one read per document and exhausted the
production project's Spark-plan daily quota (HTTP 429) part-way through.
Default mode now uses count aggregations (~1 read per 1,000 matches) plus
targeted queries: prefix ranges for the pre-2024 dotted names, an IN filter
for the five retired field types, and < 3 comparisons for pre-Django-3
versions. Only offending documents are fetched, and their owning project is
found by querying the parent maps upward (3 reads per field, capped at 100).
--full keeps the per-document scan for dangling refs/orphans/missing types,
with the read cost printed first. 429s now explain the quota and its reset.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge
Firestore rejects unquoted path segments that do not match
[a-zA-Z_][a-zA-Z_0-9]*, and auto ids can start with a digit
(fields.02haSl3F41qosZRsDUp6 -> HTTP 400). Always backtick-quote the id.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fhe72RA6v4kAgNWgxm9fge

This branch has not been deployed

No deployments
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.

1 participant