Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,19 @@ An empty database is a bad test. Seed your worktree's `.t3` with a copy of real
- One concern per PR. If the description says "also", split it.
- When babysitting: poll checks and comments newer than the last push, verify each bot finding against the source, fix real ones, dismiss false positives with a written reason. Stay quiet when nothing is new. Stop when the bots are green on the latest commit.

## Public repository boundary

Keep deployment-specific infrastructure out of this public repository. Do not
commit real machine names, private endpoints, host roles, network topology,
private ports, operator schedules, or paths that identify a maintainer's
environment. Public automation must expose environment-neutral tools and
document their contracts with generic examples. Put the concrete wiring and
deployment policy in the private infrastructure repository that owns it.

Before publishing a LastCode change, inspect the diff, PR body, issue text, and
review replies for infrastructure details. Test fixtures must use unmistakably
generic names such as `workstation.example`, `managed-server`, or `build-host`.

## Plans and work artifacts

- Do not commit implementation plans, research notes, or agent scratch files. Keep temporary working material outside the worktree. `.plans/` is gitignored only as a safety net for legacy tooling.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("deriveLastCodeEnvironmentSettingEntries", () => {
entry(
new PrimaryConnectionTarget({
environmentId: primary,
label: "Airy",
label: "Workstation",
httpBaseUrl: "http://localhost",
wsBaseUrl: "ws://localhost/ws",
}),
Expand Down Expand Up @@ -65,7 +65,7 @@ describe("deriveLastCodeEnvironmentSettingEntries", () => {
expect(
deriveLastCodeEnvironmentSettingEntries({ entries, primaryEnvironmentId: primary }),
).toEqual([
{ environmentId: primary, kind: "local", label: "Airy (local)" },
{ environmentId: primary, kind: "local", label: "Workstation (local)" },
{ environmentId: production, kind: "remote", label: "Production" },
{ environmentId: buildbox, kind: "remote", label: "Buildbox" },
{ environmentId: ssh, kind: "remote", label: "SSH Lab" },
Expand Down
16 changes: 8 additions & 8 deletions apps/web/src/environmentIcons.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,17 @@ describe("environment icon preferences", () => {
isPrimary: true,
isDesktopLocal: false,
showLocalEnvironmentIcon: false,
environmentLabel: "Airy",
environmentLabel: "Workstation",
}),
).toEqual({ kind: "monitor", showRowIcon: false, hoverLabel: null });
expect(
legacyThreadEnvironmentPresentation({
isPrimary: true,
isDesktopLocal: false,
showLocalEnvironmentIcon: true,
environmentLabel: "Airy",
environmentLabel: "Workstation",
}),
).toEqual({ kind: "monitor", showRowIcon: true, hoverLabel: "Airy (local)" });
).toEqual({ kind: "monitor", showRowIcon: true, hoverLabel: "Workstation (local)" });
expect(
legacyThreadEnvironmentPresentation({
isPrimary: false,
Expand All @@ -74,7 +74,7 @@ describe("environment icon preferences", () => {
});

it("formats the resolved local label with a safe fallback", () => {
expect(formatLocalEnvironmentLabel("Airy")).toBe("Airy (local)");
expect(formatLocalEnvironmentLabel("Workstation")).toBe("Workstation (local)");
expect(formatLocalEnvironmentLabel(" ")).toBe("Local (local)");
});
});
Expand All @@ -87,13 +87,13 @@ describe("project environment icons", () => {

it("hides a local-only icon by default and shows it after opt-in", () => {
const base = {
members: [member(local, "Airy")],
members: [member(local, "Workstation")],
primaryEnvironmentId: local,
desktopLocalEnvironmentIds: new Set<EnvironmentId>(),
};
expect(projectEnvironmentIconEntries({ ...base, showLocalEnvironmentIcon: false })).toEqual([]);
expect(projectEnvironmentIconEntries({ ...base, showLocalEnvironmentIcon: true })).toEqual([
{ environmentId: local, kind: "monitor", label: "Airy (local)" },
{ environmentId: local, kind: "monitor", label: "Workstation (local)" },
]);
});

Expand All @@ -102,7 +102,7 @@ describe("project environment icons", () => {
projectEnvironmentIconEntries({
members: [
member(buildbox, "Buildbox"),
member(local, "Airy"),
member(local, "Workstation"),
member(buildbox, "Buildbox"),
member(production, "Production"),
member(wsl, "Ubuntu"),
Expand All @@ -112,7 +112,7 @@ describe("project environment icons", () => {
showLocalEnvironmentIcon: false,
}),
).toEqual([
{ environmentId: local, kind: "monitor", label: "Airy (local)" },
{ environmentId: local, kind: "monitor", label: "Workstation (local)" },
{ environmentId: buildbox, kind: "server", label: "Buildbox" },
{ environmentId: production, kind: "server", label: "Production" },
{ environmentId: wsl, kind: "container", label: "Ubuntu" },
Expand Down
110 changes: 45 additions & 65 deletions docs/lastcode/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ stopping LastCode:

```bash
pnpm lastcode:intel-stage stage
pnpm lastcode:intel-stage stage --maximum-version-host airy
pnpm lastcode:intel-stage stage --maximum-version-host version-source.example
pnpm lastcode:intel-stage status
```

Expand All @@ -225,81 +225,61 @@ checks and supersession; a mismatch, competing invocation, or interrupted check
leaves the prior pending selection intact. Staging never closes admission, stops
the app, or starts installation.

`--maximum-version-host airy` reads `/Applications/LastCode.app` on the named
`--maximum-version-host version-source.example` reads
`/Applications/LastCode.app` on the named
SSH host and stages only releases at or below that installed nightly. If SSH is
unavailable or the remote version is not a LastCode nightly, staging stops
before changing the current pending selection. This is the simple version
ceiling used to keep htulo from moving ahead of airy.
before changing the current pending selection.
The SSH read is non-interactive and requires key-based access; it never opens a
password prompt.

### Daily htulo updates

Htulo runs the packaged LastCode server as a login LaunchAgent on the fixed
loopback port `3773`. It uses the normal LastCode home and does not launch an
Electron window, renderer, or GPU process:

```bash
pnpm lastcode:headless-service install
pnpm lastcode:headless-service status
pnpm lastcode:headless-service stop
pnpm lastcode:headless-service start
```

The first `install` writes and loads the LaunchAgent, then quits a running
LastCode desktop once so the headless server can take ownership of the normal
LastCode home. Launchd keeps the new service armed while the desktop exits.
Later service starts and daily updates do not launch or quit the desktop.

The service executes the server entry inside `/Applications/LastCode.app`, so
the running server is always the version installed on htulo. Do not launch the
desktop app against `~/.lastcode` while this service owns that home.

Htulo's external updater is a second login LaunchAgent:

```bash
pnpm lastcode:daily-update install
pnpm lastcode:daily-update status
pnpm lastcode:daily-update run-now
### Deployment primitives

This repository provides narrow components that private or organization-owned
infrastructure can compose:

- `lastcode:intel-stage` validates and prepares a published Intel build without
activating it;
- `lastcode:headless-service` runs the packaged server in a dedicated macOS x64
environment;
- `lastcode:install` performs the guarded application swap and rollback; and
- `lastcode:managed-checkout` aligns an explicitly automation-owned checkout
with a configured remote branch.

The repository deliberately does not define a real deployment topology,
machine roles, update schedule, service ordering, or concrete environment
paths. Infrastructure code owns those decisions, including when to pause work,
how to select a version ceiling, when to activate a staged app, and which
checkout is reserved for automation.

The managed-checkout tool accepts an absolute JSON configuration:

```json
{
"backupRefPrefix": "refs/example/managed-checkout-backups",
"branch": "release",
"gitCommonDirectory": "/srv/example/repository.git",
"remote": "origin",
"remoteBranch": "release",
"worktree": "/srv/example/managed-checkout"
}
```

At 04:00 each day it prepares and validates the newest eligible Intel app before
interrupting work. If an update is ready, it uses htulo's local
`lastcode-thread` command to ask every working thread to pause, waits for the
explicit `PAUSED FOR LASTCODE UPDATE` replies, checks once for a newly started
thread, stops the headless server, swaps the app, starts the same service on
port `3773`, verifies the exact installed x64 build plus its packaged server
through the public environment descriptor, and tells the paused threads to
resume. If the new service does not become ready, the installer restores the
previous app and restarts its server.
A missing pause reply leaves the prepared update in place for the next run and
does not stop the server. Threads that may already have paused are always
queued for a resume; undelivered resumes are kept in
`~/.lastcode/daily-update/pending-resumes.json` and retried before the next
daily update check.

The first update from a LastCode version older than the thread command is a
manual bootstrap after the user has paused work:

```bash
pnpm lastcode:daily-update run --bootstrap
```

`--bootstrap` is never present in the scheduled LaunchAgent. Remove the schedule
with `pnpm lastcode:daily-update uninstall`.

For occasional desktop UI QA on htulo, stop any prior QA instance and run one
isolated desktop process. Its state and port are deliberately separate from the
production server:
Run it with:

```bash
T3CODE_HOME="$HOME/.lastcode-qa" \
T3CODE_PORT=4773 \
/Applications/LastCode.app/Contents/MacOS/LastCode
pnpm lastcode:managed-checkout sync --config /absolute/path/checkout.json
```

Quit that desktop when QA is complete. Airy and browser clients continue to use
the production htulo environment on port `3773` throughout.
The tool verifies the configured repository identity, selected branch, clean
tracked and untracked state, inactive Git operation, initialized-submodule
changes, and collisions with ignored content. It fetches only the configured
remote branch, saves the old tip under the configured backup-ref prefix, and
moves the branch with compare-and-swap semantics before updating the tree. The
caller must give it exclusive ownership of the checkout for the duration of the
operation; Git cannot lock arbitrary concurrent filesystem writes. If the ref
moves but tree verification fails, the tool retains the target ref and reports
the backup ref for explicit recovery instead of pretending it rolled back.

State defaults to `~/.lastcode/intel-updates`. `pending.json` is the narrow,
credential-free handoff contract for later drain and activation work. It names
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"lastcode:install": "mise exec node@24.13.1 -- node scripts/lastcode-install.mjs",
"lastcode:intel-build": "mise exec node@24.13.1 -- node scripts/lastcode-build-intel-package.ts",
"lastcode:intel-stage": "mise exec node@24.13.1 -- node scripts/lastcode-intel-stage.mjs",
"lastcode:daily-update": "mise exec node@24.13.1 -- node scripts/lastcode-daily-update.mjs",
"lastcode:managed-checkout": "mise exec node@24.13.1 -- node scripts/lastcode-managed-checkout.mjs",
"lastcode:headless-service": "mise exec node@24.13.1 -- node scripts/lastcode-headless-service.mjs",
"lastcode:setup": "mise exec node@24.13.1 -- node scripts/lastcode-setup.mjs",
"lastcode:checkpoint:service": "mise exec node@24.13.1 -- node scripts/lastcode-nightly-service.ts",
Expand Down
1 change: 0 additions & 1 deletion scripts/lastcode-daily-intel-package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ describe("lastcode-daily-intel-package", () => {
expect(workflow).toContain("group: lastcode-daily-intel-package");
expect(workflow).toContain("cancel-in-progress: false");
expect(workflow).toContain("node scripts/lastcode-daily-intel-package.ts");
expect(workflow).not.toMatch(/airy|htulo/u);
});

it("selects the newest strict installable and peels annotated tags", () => {
Expand Down
Loading