Skip to content

refactor(project): one builder and parser for project addresses; short links for GitHub projects (G-A1c) - #187

Merged
trakhimenok merged 5 commits into
mainfrom
apps-g-a1c-sweep
Oct 3, 2026
Merged

trakhimenok merged 5 commits into
mainfrom
apps-g-a1c-sweep

Conversation

@trakhimenok

Copy link
Copy Markdown
Contributor

What

G-A1c of docs/design/demo-as-github-project.md (backstage, section 8): every place that builds or parses a project path uses parseProjectUrl / projectUrl / tryProjectUrl (libs/datatug/project-address, re-exported from libs/datatug/main/src/lib/nav/), found by content, not by the list in 3.4. A GitHub project's in-app links are at its short address (/project/github.com/<owner>/<repo>[/tree/<ref>[/<dir>]/-/<page>]). The old /store/github.com/project/<id> form still opens, with no redirect (G-A1d is held). Agent-store and Firestore-store projects keep their addresses and behaviour.

Based on a2481560 (G-A1b, #183). Three commits: the library change, the sweep, the e2e.

Sites changed (file:line of the new code, before -> after)

Library libs/datatug/project-address/src/lib/project-url.ts

  • tryProjectUrl / projectUrl take the page as text ('queries/x', split at each /) or as segments (['query', 'customers/invoices'], one element = one segment, a / in it is written %2F). DEMO_PROJECT_REF (chinook-demo@datatug@) added.

libs/datatug/main/src/lib/services/nav/datatug-nav.service.ts (the builders named in 3.4: :84, :116-125, :201-206, plus goProjPage, goTable, goEntity...)

  • goProject :87, goEnvironment :110, goCatalog :129, goEntity :149, goQuery :168, goBoard :206, goProjPage :260, goTable :277: ['store', storeId, 'project', id, ...] arrays and /store/${...}/project/${...} strings -> tryProjectUrl(ref, page). A project with no exact address is logged and not navigated to a guess.
  • projectPageUrl :224: /store/${getStoreId(..)}/project/${..}/<name>[/<id>] -> projectPageHref (same address for agent stores).
  • libs/datatug/main/src/lib/nav/project-page-href.ts (new): projectPageHref(ref, page) = the address, or / when there is no project / no exact address. Used by every template link below.

libs/datatug/main/src/lib/services/nav/datatug-nav-context.service.ts

  • :35-:55 the regexes reStore, reProj (and the legacyShapeOf rewrite to the old shape) -> locationOf :302: parseProjectUrl gives store, project and the page after it; reEnv/reEnvDb/reTable now run on that page only (a ?query is no longer part of an environment or a table). Short addresses still need the short route to have opened them (unchanged rule).

Back links and navigations

  • pages/signed-in/entity-edit/entity-edit-page.component.ts:109 `/store/${storeId}/project/${projectId}/entities` -> projectPageHref(ref, 'entities')
  • queries/queries/queries-page.component.ts:115 (defaultBackHref) -> projectPageHref(ref)
  • queries/query/page/query-page.component.html:5 + .ts (queriesBackHref), queries/query/sql-query/sql-query-editor.component.html:5 + .ts: defaultHref="/store/{{..}}/project/{{..}}/queries?folder=.." -> [defaultHref]="queriesBackHref"
  • pages/signed-in/servers/servers-page.component.ts:280 (goDbServer): ['store', .., 'project', .., 'servers', 'db', driver, id] -> tryProjectUrl(ref, [...])
  • pages/signed-in/environment/environment-page.component.ts:114 (defaultBackUrl, was the constant /store/localhost:8989 for every project) -> the store page of the project's own store; the old constant until a project is known (agent on localhost:8989: identical). :209 (goEnvSubPage, was /project/<id>/env/.., a path that matched no route) -> tryProjectUrl.
  • pages/signed-in/env-db-table/env-db-table.page.ts:599 and pages/signed-in/investigation-context/investigation-context-page.component.ts:682 (open a query): router.navigate(['/store', .., 'project', .., 'query', encodeURIComponent(id)], {queryParams}) -> router.navigateByUrl(tryProjectUrl(ref, ['query', id]) + '?id=' + encodeURIComponent(id), {state})
  • pages/signed-in/project/project-page.component.ts:222 (goProjFolder, no caller; was project/<id>/<items>) -> tryProjectUrl
  • pages/signed-in/store/datatug-store-page.component.ts:436 (projectLink) + .html:67: [routerLink]="'project/' + project.id" -> projectLink(project) (short address for GitHub). :498: `${repository}@${owner}@${folder}` -> formatGithubProjectId.
  • project/new-project/new-project-form.component.ts:300: `${repo}@${org}@${folder}` -> formatGithubProjectId (the one id: lower case owner/repo, default folder dropped).
  • incidents/incident-evidence-targets.ts:66-142: ['/store', storeId, 'project', id, ...] -> [tryProjectUrl(..), ...page segments] (router commands, so the page segments stay raw for the router to encode).
  • routes/github-project-address-check.ts:212 (defaultBranchPath): the hand-written /project/github.com/<o>/<r>/tree/ prefix and string surgery -> projectUrl.

Home page and demo entry

  • pages/home/my-projects/my-datatug-projects.component.ts:72: datatug-demo-projects@datatug@demo-project-1 -> DEMO_PROJECT_REF: opens /project/github.com/datatug/chinook-demo. Title unchanged.
  • pages/signed-in/store/datatug-store-page.component.ts:82 (the store page's copy of the same entry, "kept in sync by hand") -> the same constant.
  • apps/datatug-app/src/app/demo-holding-page.strings.ts:54 (DEMO_PROJECT_PATH, the holding page's "Open the demo project" link): the old-form array -> projectUrl(DEMO_PROJECT_REF).

App

  • apps/datatug-app/src/main.ts:103: location.pathname.match(/\/store\/([^/]+)/) with a fall back to localhost:8989 -> agentBaseUrlOfPath(pathname) (src/app/agent-base-url.ts): any /project/... address and the GitHub store (either id) get no agent (about:blank, not an address a request can reach; the semantic client's agent context throws on an empty base); /store/<id> as before; anything else the local agent as before. A GitHub project at its short address used to get http://localhost:8989/datatug (the page called localhost:8989); at the old address it got //github.com:undefined/datatug.
  • apps/datatug-app/src/app/cli-chat-capability.ts:4 left as it is, with cli-chat-capability.spec.ts: the short address (and look-alikes) do not match.

Left as is, with the reason

  • project-page.component.html:6 ('/store/' + storeId) and environment-page.component.ts:114: they write the address of a STORE page, which stays /store/<id> in both shapes; not a project address. Both are allowed files of the guard (with cli-chat-capability.ts), each with its reason.
  • board-page.component.ts:268, boards-page.component.ts:194: back links computed by dropping the last path segment of location.pathname; they work on either shape, equally (the boards one was already odd for the old shape).
  • datatug-nav.service.ts goStore: a store address.

The regression guard (design 3.4, point 3)

libs/datatug/project-address/src/lib/project-address-guard.spec.ts:

  • the short and the old spelling of the same project resolve to the same {storeId, projectId, rest}, and the old one's canonical address is the short one (6 projects); every other store has the old shape only;
  • no third shape: 14 inputs that are not project addresses are refused; projectUrl writes only the two shapes;
  • a scan of apps/datatug-app/src and every libs/datatug/*/src (300+ files, comments stripped) fails on a hand-built project path (`/store/${`, '/store/' +, /store/{{, ['store', .., 'project'], `/project/${`, regexes over /store/ or /project/, .split('@'), a repo@owner join). Three allowed files, each with its reason; each must still match. Checked by mutation: a `/store/${s}/project/${p}` added to a source file fails it.
  • the held part (the old address redirecting) belongs to G-A1d.

Checks

  • Unit: datatug-main 146 files / 2109 tests, datatug-app 16 / 616, datatug-project-address 4 / 445: green. Every changed branch of the sources is covered (checked line by line against the coverage JSON). lint (the three projects): clean, no finding. pnpm run check:zoneless: OK.
  • e2e against the production build, served statically with a SPA fallback: demo-handoff config 82 / 82 (the link assertion changed, nothing else); github-store.spec.ts 9 / 11 on the production build, the 2 others read component state through window.ng.getComponent, which a production build does not have; all 11 pass on the development server (DATATUG_E2E_GITHUB_FAKE, below).
  • journey suite (real datatug serve 0.51.0, dev server): 17 / 17. The default chromium project (agent-mocking suites + github-store): 36 passed, 1 skipped.
  • github-store.spec.ts now opens the demo-projects project at its short address, and has three new cases (side menu of a short address links to short addresses; the old address opens as typed and its in-app links are short; the home page's demo entry opens /project/github.com/datatug/chinook-demo). DATATUG_E2E_GITHUB_FAKE=<datatug-demo-projects checkout> (e2e/helpers/fake-github.ts) serves that checkout in place of the three GitHub hosts; unset (CI), the spec reads the real GitHub as before.

Screenshots: overview, queries, chat; 1280 and 390; agent project and GitHub project

Production builds of origin/main (a248156) and of this branch, side by side; the agent project is a real datatug serve (the demo project) on one port for both; the GitHub project is the demo-projects project served by the fake in both. Each page is a fresh browser context. 30 images, compared pixel by pixel:

  • agent project, main vs branch (same address): 6 / 6 byte-identical;
  • GitHub project, main at the old address vs branch at the short address: 6 / 6 byte-identical (only the address bar differs, and it is not in the image);
  • GitHub project, main vs branch both at the old address: 6 / 6 byte-identical.
    Files: .../scratchpad/apps-g-a1c/shots/<agent|github>-<base|new|new-short|new-old>-<overview|queries|chat>-<1280|390>.png.

Initial bundle (production build)

main this branch
main-*.js raw 1,227,474 1,227,771 (+297)
initial chunks raw (index.html) 1,804,053 1,804,281 (+228)
initial chunks gzip -9 495,866 495,973 (+107)
Angular "Initial total" estimated transfer 470.80 kB 471.04 kB

First attempt (agentBaseUrlOfPath using parseProjectUrl and importing GITHUB_STORE_ID) cost +7 kB raw: the parser was not in the initial bundle at all, and importing a constant kept all of github-project-address.ts (+2.9 kB). So agentBaseUrlOfPath reads the store only, with the two ids written out (a spec asserts they equal the library's).

Design and reality

  • 3.4 says main.ts:103 should use the two functions; doing so costs ~7 kB in the initial bundle against "a few hundred bytes". It reads the store only (it never needed the project), so it does not use the parser; a GitHub project still never gets an agent address.
  • 3.4 lists project-page.component.html:6 and environment-page.component.ts:110 as hard-coded old shapes; both write a store address (see above). The environment page's fixed localhost:8989 back link is now the project's own store.
  • Journey suites (e2e/journey/*): agent addresses are unchanged, so nothing to rewrite there; the fixture still mounts datatug-demo-projects/demo-project-1 (the table's "repoint to the new repo" waits for the CLI to be able to serve chinook-demo at its root; I could not check that here, there is no chinook-demo checkout). Passing 17 / 17 as they are.
  • The hand-off page's "Open the demo project" link now goes to the short address of chinook-demo, as the home page's entry does (its own comment says they must match). The hold page, the question handling and the safety net are untouched (82 / 82).
  • The store page's "Open a GitHub project" form keeps its pre-filled datatug-demo-projects / demo-project-1: the form requires a folder, so it cannot open a project at a repo root.
  • Issue Short project addresses: follow-ups from the reviews of #183 #184: no item is closed by this change. (legacyShapeOf is gone, replaced by locationOf; it was not an item of the issue. The ion-header e2e item was already fixed in feat(project): short address for GitHub projects (G-A1b) #183.)

Not verified

  • Real GitHub: only 2 tests of label-spacing.spec.ts (no fake) ran against it, about 4 anonymous API calls in all (2 per cold visit), within the default chromium run.
  • Incidentius in a browser (its links use tryProjectUrl for agent stores; the short route does not match under that profile, so a GitHub project in an incident link would not open there; incident links are agent-only today).
  • The two github-store.spec.ts cases that need window.ng on the production build; they pass on the development server.

🤖 Generated with Claude Code

OpenVaultDB and others added 4 commits October 3, 2026 07:11
… (G-A1c)

projectUrl/tryProjectUrl take the page as plain text ('queries/x', split at
each '/') or as its segments (['query', 'customers/invoices']): an element is
ONE segment whatever it holds, so a folder-qualified query id is written
`%2F` and addressed by one `query/:queryId` route. The sites of the sweep
need this; the text form is unchanged.

DEMO_PROJECT_REF is the demo project (datatug/chinook-demo, the repository
root): the one place the home page, the store page and the hand-off page get
its id from. Its address is /project/github.com/datatug/chinook-demo, and it
is the one trusted project of isTrustedProjectAddress.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…t links for GitHub projects (G-A1c)

Every place that builds or parses a project path now uses projectUrl /
tryProjectUrl / parseProjectUrl, found by content. A GitHub project's
in-app links are at its short address (/project/github.com/<owner>/<repo>
[/tree/<ref>[/<dir>]/-/<page>]); the old /store/github.com/project/<id> form
still opens (redirecting it is G-A1d, held). Agent and Firestore projects
keep their addresses and behaviour.

datatug-main:
- DatatugNavService (goProject, goProjPage, goEnvironment, goCatalog,
  goEntity, goQuery, goBoard, goTable, projectPageUrl) writes its address
  with tryProjectUrl; a project with no exact address is logged, not
  navigated to a guess.
- DatatugNavContextService reads the store, project and page with
  parseProjectUrl instead of regular expressions over the whole URL (a query
  string is no part of an environment or a table any more).
- back links and router commands: entity-edit, queries, query page, SQL
  editor, servers (goDbServer), environment page, project page folder,
  investigation context and table page (open query), incident evidence
  links; projectPageHref is the one helper for a link in a template.
- the new-project form and the store page's "Open a GitHub project" form
  write the project id with formatGithubProjectId (the one id, owner and
  repo in lower case, the default folder dropped); the store page row links
  to the project's address.
- the address check's default-branch redirect is written with projectUrl.
- the home page's demo entry, and the store page's, are the demo project at
  the root of datatug/chinook-demo (DEMO_PROJECT_REF).

datatug-app:
- main.ts: the agent base URL comes from agentBaseUrlOfPath (store only, so
  the parser is not pulled into the initial bundle): a GitHub project, at
  either address, has no agent. It used to get localhost:8989 on its short
  address.
- the hand-off page's "Open the demo project" link is the demo project's
  short address.
- cli-chat-capability.ts is left as is, with a test that the short address
  does not match it.

Guard: project-address-guard.spec.ts scans the sources for hand-built project
paths and GitHub id joins, and proves the short and old spellings of a
project resolve to the same {storeId, projectId}.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…github-store (G-A1c)

- github-store.spec.ts opens the demo-projects project at
  /project/github.com/datatug/datatug-demo-projects/tree/HEAD/demo-project-1
  (its pages follow /-/), and gains three cases: the side menu of a project
  opened at its short address links to short addresses; the old address
  still opens it, with no redirect, and its in-app links are short; the home
  page's demo entry opens /project/github.com/datatug/chinook-demo.
- DATATUG_E2E_GITHUB_FAKE=<a datatug-demo-projects checkout> serves that
  checkout in place of api.github.com, raw.githubusercontent.com and
  cdn.jsdelivr.net (helpers/fake-github.ts), so the spec needs no network and
  spends none of the 60 anonymous API requests an hour. Unset, it reads the
  real GitHub as before (CI).
- demo-handoff.spec.ts: the holding page's "Open the demo project" link is
  the demo project's short address.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@trakhimenok

Copy link
Copy Markdown
Contributor Author

[review r1 #187] Adversarial review (Opus): production builds of this head, main and the merge with #186 served side by side; click-through of every changed navigation for agent (real datatug serve), Firestore and GitHub projects on both address forms; the demo entry against the real datatug/chinook-demo; the two CI e2e failures repeated 82 times on production builds with the GitHub fake.

Reviewed-Head: 5a1e253

Agent and Firestore projects navigate the same as on main; GitHub projects navigate correctly from both forms; switching between projects never loses or swaps the project; query ids with a slash open the right query; the agent base URL is unchanged for every agent address and no GitHub page calls the local agent; the demo entry renders boards, entities, queries, environments, servers and chat with no console errors; initial bundle +287 B. The two CI e2e failures did not reproduce (82/82); no new race found; likely GitHub rate limits on shared runners. Merge with #186 is clean (demo-handoff e2e 113/113 on the merged tree).

Minors, carried to #184:

  1. The regression guard (libs/datatug/project-address/src/lib/project-address-guard.spec.ts:139-170) caught one of five hand-built paths in a mutation test; exceptions cover whole files. Make exceptions per occurrence; add patterns for }/project/${, ['project', and string arguments to RegExp(.
  2. demo-handoff-path.ts (routeSegments, handoffTarget) and its ES5 twin in index.html parse the short address by hand, and feat(demo): start-chat confirmation page, question after # (founder ruling 2026-10-03) #186 adds a start-chat builder; record them as explicit guard exceptions with the reason.
  3. Stale comments: main.ts and agent-base-url.spec.ts:10 say agentBaseUrlOfPath() uses parseProjectUrl (it does not, deliberately); agent-base-url.spec.ts:82 compares against a literal 'github'.
  4. Small agent address changes: the environment page's no-history back link goes to the project's own store (/store/http-localhost:8989); records keyed by the old full-URL store id now open under the canonical id, so chats saved under the old key will not show. An id with a space is now encoded once (fixes double encoding on main).
  5. Agent project ids containing %, ?, #, / or edge spaces are refused instead of navigated; encode the segment or document them as unsupported.
    After feat(demo): start-chat confirmation page, question after # (founder ruling 2026-10-03) #186: demo-holding-page.component.spec.ts (~351-382) fakes leaving to /store/github.com/project/p1; update to the new demo address.

Not verified: Firestore projects with real data, signed-in store rows, Cloudflare's %2F decoding.

VERDICT: blockers=0 majors=0 minors=5 land=yes

🤖 Generated with Claude Code

@trakhimenok
trakhimenok merged commit 0d426c1 into main Oct 3, 2026
4 checks passed
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