Skip to content

feat: start a new project from just a name (upstream) - #993

Merged
rynfar merged 2 commits into
pylonfrom
upstream/2026-10-03-new-project
Oct 3, 2026
Merged

rynfar merged 2 commits into
pylonfrom
upstream/2026-10-03-new-project

Conversation

@rynfar

@rynfar rynfar commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Adapts upstream "start a new project from just a name" for Pylon as New project, distinct from Add existing project (add-folder and clone flows are unchanged and reachable from the new step).

Source

Upstream Title Outcome
148e6deea046658639aae9fef5b349781cec39d1 (#14527) feat: start a new project from just a name Adopted with adaptations (below)

Re-grounded against orchestrator v2: the server operation (ManagedProjectFolders.createNamedProject), the projects.createNew RPC, its operate-scope authorization, the shared newProjectFolderName helper and the Pylon-branded README already arrived with the v2 adoption. Upstream's apps/server/src/project/NewProject.ts, its ws.ts wiring and server.test.ts case were therefore not ported; the rest of the commit was cherry-picked (-x) and adapted.

Adaptations

  • Contracts: ServerConfig.newProjectsRoot (optional) is now part of the schema, so the root the server already returned actually reaches clients; ProjectCreateNewInput.projectId (optional) carries a client-chosen id. Older servers ignore the field (default excess-property handling); older clients omit it and get the previous non-idempotent behavior.
  • Idempotency (design review requirement): creates with the same projectId are serialized per id; a retry after a lost response, or a duplicate sent while the first runs, replays the project the first attempt made instead of claiming a -2 folder. Only a live project inside the named-projects folder replays; any other owner of the id fails with NamedProjectIdInUseError, sent on the wire as the new ProjectCreateNewIdInUseError (only for creates carrying a projectId, so older clients never see it). Each attempt uses a fresh command id, so a retry after a rejection is planned again rather than resolved to the old rejected receipt. A replay reports a missing first commit from the repository itself.
  • Cleanup boundary: a claimed folder is removed only when the committed state proves no project owns it (neither the requested id rooted at that folder nor any project at that root; an id owned elsewhere no longer orphans the scaffold). This now also covers an interrupt during dispatch, which previously left an orphan folder; an interrupt after the commit keeps the folder and the retry returns the project. If the state cannot be read, the folder stays.
  • Storage root: <server baseDir>/projects, i.e. derived from Pylon's runtime home (~/.pylon-code/projects by default), advertised per environment; no T3 path.
  • Starter files: README says "Created in Pylon"; neutral initials icon at assets/icon.svg; first commit on init.defaultBranch, else main. Commit failure keeps the folder and surfaces a warning on every client.
  • GitHub publishing: optional private repository via the selected environment's sourceControl.publishRepository, using the actual (possibly suffixed) folder from the response. Failure keeps the local project and points at Publish repository. Pylon change: publishing is skipped (with a visible explanation) when the first commit failed, since an unborn repository has nothing to push.
  • Folder names: newProjectFolderName also refuses COM0/LPT0 (superscript forms decompose via NFKD); added Unicode/Windows tests. Non-Latin names fall back to project as upstream, keeping folders and GitHub repository names portable; titles keep the name as typed.
  • Web/desktop: palette step from the root ("New project") and from Add project sources; environment switcher keeps the typed name; Enter respects IME composition; "Add existing project" returns to sources. Sidebar/draft "New project" buttons that open Add project are relabeled "Add project".
  • Mobile: add-project/new route ("New Project" title), screen reusing the add-project environment options, waitForProject before opening the draft; Pylon's fixed icon sizes instead of upstream's Platform sizing.
  • Client idempotency: createNewProjectAttemptStore (client-runtime) keeps unfinished attempts keyed by environment and trimmed/NFC name. Web and mobile hold one at module scope, so closing the palette or leaving the screen keeps the id (until reload). Attempts settle on confirmed success (web) / once the draft opens (mobile), or on ProjectCreateNewIdInUseError; other failures keep the id so a retry replays.
  • Capability signal: no new flag. ServerConfig.newProjectsRoot is the gate: the server returned it since the v2 adoption, but the contract never declared it on origin/pylon (git log -S newProjectsRoot -- packages/contracts/src/server.ts is empty), so earlier servers strip it on encode and their clients never offer New project. Any server that advertises it honors projectId. Documented on the schema field.
  • Mobile "Add existing project": goes back only when the Add project screen underneath shows the selected machine; otherwise replaces it with sources for the selected machine (add-project now takes an optional environmentId).
  • Docs: docs/user/source-control.md describes the Pylon behavior, location, retry, commit and publishing failure handling.

Kept additive for the parallel Scratch port: createNew is appended after writeFile in projectCommands.ts; newProjectsRoot sits after rollbackRecoveryOperationIdentity in ServerConfig; no Scratch code was touched.

Verification

  • vp run -F t3 typecheck, vp run -F @t3tools/contracts typecheck, vp run -F @t3tools/shared typecheck, vp run -F @t3tools/client-runtime typecheck, vp run -F @t3tools/web typecheck, vp run -F @t3tools/mobile typecheck: each ran tsc --noEmit, exit 0.
  • apps/server: vp test run src/project/ManagedProjectFolders.test.ts src/mcp/toolkits/project/handlers.test.ts: 27 passed. New cases: custom init.defaultBranch, commit-signing failure, Unicode/Windows folder names, retry replay, concurrent duplicates joined, replayed missing-commit warning, foreign id refused, rejected-then-retried, interrupted before commit (folder removed), interrupted after commit (kept, retry replays), id taken elsewhere mid-create (scaffold removed). Interrupt tests synchronize on Deferreds, no sleeps.
  • packages/shared: vp test run src/path.test.ts: 9 passed.
  • packages/contracts: vp test run src/project.test.ts: 16 passed (incl. id-in-use error round-trip).
  • packages/client-runtime: vp test run src/operations/projects.test.ts: 23 passed (path preview incl. Windows, suffixed publish target, GitHub readiness, attempt store reuse/normalization/settlement, id-in-use detection).
  • apps/web: vp test run src/components/sidebar/SidebarThreadHeader.test.tsx: 2 passed. apps/mobile: vp test run src/features/projects/AddProjectScreen.logic.test.ts: 6 passed (incl. Add existing project navigation).
  • vp lint on changed TS/TSX: no new warnings (CommandPalette keeps its 6 pre-existing warnings). vp fmt --check on changed files: clean.

Unverified

  • No UI, browser, desktop, simulator or device run (not authorized for this task): palette keyboard flow, mobile screen layout, deep link/back navigation, and the draft opening after projection are untested at runtime.
  • GitHub publishing was not exercised against a real gh account.
  • Remote/tunnel environments were not exercised live; clients route through the selected environment's RPC as with clone.
  • Retry protection is in-memory on the client: a page/app reload between a lost response and the retry mints a new id.
  • A server crash between folder claim and commit can still leave an orphan folder (a retry then claims -2); only a durable provisioning intent would close that, which is out of scope.

Part of upstream cycle #981.

🤖 Generated with Claude Code

Adapts upstream 148e6deea046658639aae9fef5b349781cec39d1 (#14527) for
Pylon. The server operation, RPC and scope already shipped with the
orchestrator v2 adoption (ManagedProjectFolders.createNamedProject); this
adds the missing contract field, idempotency, interruption handling and
the client flows.

- contracts: advertise ServerConfig.newProjectsRoot; optional
  client-chosen projectId on projects.createNew.
- server: a retry or concurrent duplicate with the same projectId replays
  the project the first attempt made (serialized per id, fresh command id
  per attempt so a retry after a rejection is re-planned); a claimed folder
  is removed only when the committed state proves no project owns it,
  including after an interrupt during dispatch.
- shared: newProjectFolderName also refuses COM0/LPT0; Unicode and
  Windows device-name coverage.
- client-runtime: path preview, GitHub target/repository helpers, and
  resolveNewProjectAttempt for idempotent resubmits; createNew command.
- web/desktop: New project palette step beside Add existing project, with
  environment switcher and optional private GitHub publishing.
- mobile: add-project/new route and screen.
- docs: Pylon-branded user docs in docs/user/source-control.md.

(cherry picked from commit 148e6deea046658639aae9fef5b349781cec39d1)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 4.9 KiB 4.9 KiB 0 B (0.0%) 6.8 KiB ✅
Codex Thread snapshot wire 3.7 KiB 3.7 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.1 KiB 1.1 KiB 0 B (0.0%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 20.4 KiB 20.4 KiB 0 B (0.0%) 29.3 KiB ✅
Codex Live turn messages 1 1 0 (0.0%) 8 ✅
Claude Total thread wire 4.9 KiB 4.9 KiB +41 B (+0.8%) 6.8 KiB ✅
Claude Thread snapshot wire 3.7 KiB 3.7 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB +41 B (+3.5%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 20.7 KiB 20.8 KiB +41 B (+0.2%) 29.3 KiB ✅
Claude Live turn messages 1 2 +1 (+100.0%) 8 ✅

Baseline: b4c210e · PR result: 59a17d7 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 106.1 KiB
  • Claude decoded thread snapshot: 106.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Review follow-ups for the New project port:

- Pending create attempts live in a client-runtime store held at module
  scope on web and mobile, keyed by environment and normalized name, so
  closing the palette or leaving the screen no longer mints a new id.
- projects.createNew reports a refused id as ProjectCreateNewIdInUseError;
  clients settle the attempt on it instead of looping on the same id.
- Mobile keeps the attempt until the draft opens, so Create after a
  projection timeout replays the project rather than duplicating it.
- A claimed folder is kept only when the project with the requested id is
  rooted at that folder; an owner elsewhere no longer orphans the scaffold.
- Mobile "Add existing project" opens sources for the machine selected on
  the New project screen.
- Document newProjectsRoot as the capability gate for retry replay.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@rynfar
rynfar merged commit 9c99f33 into pylon Oct 3, 2026
23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant