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
158 changes: 158 additions & 0 deletions .github/fork-features.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
version: 1
coverage: incremental

# Capabilities, not commits. Follow-up fixes belong to the feature they preserve.
features:
- id: completion-sounds
title: Configurable agent completion sounds
status: maintained
prs: [5, 7, 19, 72]
invariants:
- Users can select, preview, and disable the sound played when an agent finishes or awaits input.
- Sound playback follows persisted client settings without firing repeatedly for the same turn state.
implementation_paths:
- apps/web/src/components/TurnCompletionSound.tsx
- apps/web/src/components/settings/SettingsPanels.tsx
- apps/web/src/lib/completionSound.logic.ts
- apps/web/src/lib/completionSound.ts
- packages/contracts/src/settings.ts
upstream_paths:
- apps/web/src/AppRoot.tsx
- apps/web/src/components/settings/SettingsPanels.tsx
- packages/contracts/src/settings.ts
tests:
- apps/desktop/src/settings/DesktopClientSettings.test.ts
- apps/web/src/lib/completionSound.logic.test.ts
- apps/web/src/lib/completionSound.test.ts
- packages/contracts/src/settings.test.ts
upstream:
status: unassessed
tracking: []
retire_when: Upstream provides configurable completion and awaiting-input sounds with equivalent persistence and playback behavior.

- id: composer-draft-sync
title: Cross-device composer draft synchronization
status: maintained
prs: [39, 44]
invariants:
- Existing-thread draft text and attachments survive restarts and synchronize without overwriting newer local edits.
- Web, desktop-hosted web, and mobile use the same server-backed draft contract and fork-owned migration history.
implementation_paths:
- apps/mobile/src/state/use-composer-drafts.ts
- apps/server/src/persistence/ComposerDrafts.ts
- apps/server/src/persistence/ForkMigrations/001_ComposerDrafts.ts
- apps/web/src/composerDraftStore.ts
- packages/contracts/src/composerDraft.ts
upstream_paths:
- apps/server/src/server.ts
- apps/server/src/ws.ts
- apps/web/src/components/ChatView.tsx
- apps/web/src/components/chat/ChatComposer.tsx
- packages/client-runtime/src/rpc/client.ts
- packages/contracts/src/rpc.ts
tests:
- apps/mobile/src/state/use-composer-drafts.test.ts
- apps/server/src/persistence/ComposerDrafts.test.ts
- apps/server/src/persistence/ForkMigrations.test.ts
- apps/web/src/composerDraftStore.test.ts
upstream:
status: unassessed
tracking: []
retire_when: Upstream synchronizes equivalent composer draft state across clients and safely migrates installations carrying the fork schema.

- id: github-outage-status
title: GitHub outage status in the sidebar
status: maintained
prs: [14, 136]
invariants:
- Enabled clients distinguish GitHub incidents from missing or malformed status responses.
- The sidebar notice reports affected services without blocking ordinary source-control work.
implementation_paths:
- apps/web/src/components/settings/SettingsPanels.tsx
- apps/web/src/components/sidebar/GitHubStatusNotice.tsx
- apps/web/src/githubStatus.ts
- packages/contracts/src/settings.ts
upstream_paths:
- apps/web/src/components/settings/SettingsPanels.tsx
- apps/web/src/components/sidebar/SidebarChrome.tsx
- packages/contracts/src/settings.ts
tests:
- apps/desktop/src/settings/DesktopClientSettings.test.ts
- apps/web/src/githubStatus.test.ts
- packages/contracts/src/settings.test.ts
upstream:
status: unassessed
tracking: []
retire_when: Upstream exposes equivalent configurable GitHub incident reporting in the primary navigation.

- id: integrated-browser-links
title: Open local development links in the integrated browser
status: maintained
prs: [107, 145, 153, 156]
invariants:
- Loopback links opened from chat or terminal activity resolve on the environment machine in the integrated browser.
- Unsupported links retain an external-browser path and never strand the user behind an unavailable preview runtime.
implementation_paths:
- apps/desktop/src/preview/BrowserSession.ts
- apps/server/src/preview/TerminalBrowserOpen.ts
- apps/web/src/components/chat/loopbackLinkPreview.ts
- apps/web/src/components/preview/openTerminalLinkInPreview.ts
upstream_paths:
- apps/desktop/src/preview/Manager.ts
- apps/server/src/http.ts
- apps/server/src/terminal/Manager.ts
- apps/web/src/components/ChatMarkdown.tsx
- apps/web/src/components/ThreadTerminalDrawer.tsx
tests:
- apps/desktop/src/preview/BrowserSession.test.ts
- apps/server/src/preview/TerminalBrowserOpen.test.ts
- apps/web/src/components/chat/loopbackLinkPreview.test.ts
- apps/web/src/components/preview/openTerminalLinkInPreview.test.ts
upstream:
status: unassessed
tracking: []
retire_when: Upstream routes local chat and terminal links through an environment-aware integrated browser with an external fallback.

- id: project-filter-continuity
title: Preserve project filters while navigating and creating threads
status: maintained
prs: [83, 162]
invariants:
- Reopening the sidebar does not discard the user's project filter.
- New-thread actions follow the visible project scope without unexpectedly switching the active thread's project.
implementation_paths:
- apps/web/src/components/CommandPalette.tsx
- apps/web/src/components/Sidebar.tsx
- apps/web/src/lib/chatThreadActions.ts
- apps/web/src/sidebarProjectScopeStore.ts
upstream_paths:
- apps/web/src/components/AppSidebarLayout.tsx
- apps/web/src/components/CommandPalette.tsx
- apps/web/src/components/Sidebar.tsx
tests:
- apps/web/src/lib/chatThreadActions.test.ts
upstream:
status: unassessed
tracking: []
retire_when: Upstream preserves project scope across sidebar state and every new-thread entry point.

- id: run-shell-code-blocks
title: Run completed shell code blocks in a terminal
status: maintained
prs: [97]
invariants:
- Completed recognized shell fences expose a Run in terminal action with the exact displayed command.
- Incomplete or non-shell code blocks never expose the terminal action.
implementation_paths:
- apps/web/src/components/ChatMarkdown.tsx
- apps/web/src/components/chat/MessagesTimeline.tsx
upstream_paths:
- apps/web/src/components/ChatMarkdown.tsx
- apps/web/src/components/chat/MessagesTimeline.tsx
tests:
- apps/web/src/components/ChatMarkdown.test.tsx
- apps/web/src/components/chat/MessagesTimeline.test.tsx
upstream:
status: unassessed
tracking: []
retire_when: Upstream offers equivalent guarded terminal execution for completed shell code blocks.
3 changes: 3 additions & 0 deletions .github/workflows/fork-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ jobs:
- name: Check
run: vp check

- name: Check fork feature ledger
run: vp run --filter @t3tools/scripts ledger:check

- name: Typecheck
run: vpr typecheck

Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/fork-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ jobs:
echo "ref=$candidate_ref"
echo "fork_ref=$fork_ref"
echo "main_ref=$main_ref"
echo "old_upstream_ref=$OLD_UPSTREAM_REF"
echo "upstream_ref=$upstream_ref"
} >> "$GITHUB_OUTPUT"

Expand Down Expand Up @@ -237,6 +238,20 @@ jobs:
cache: true
run-install: true

- name: Review upstream overlap with fork features
if: steps.candidate.outputs.has_changes == 'true'
continue-on-error: true
env:
OLD_UPSTREAM_REF: ${{ steps.candidate.outputs.old_upstream_ref }}
UPSTREAM_REF: ${{ steps.candidate.outputs.upstream_ref }}
shell: bash
run: |
set -euo pipefail
changed_paths="$RUNNER_TEMP/upstream-changed-paths.txt"
git diff --name-only --no-renames \
"$OLD_UPSTREAM_REF..$UPSTREAM_REF" > "$changed_paths"
vp run --filter @t3tools/scripts ledger:check -- --changed-paths "$changed_paths"

- name: Ensure Electron runtime is installed
if: steps.candidate.outputs.has_changes == 'true'
run: vp run --filter @t3tools/desktop ensure:electron
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ policy in [CONTRIBUTING.md](../CONTRIBUTING.md); agent rules in [AGENTS.md](../A
- [Workspace layout](./internals/workspace-layout.md)
- [Glossary](./internals/glossary.md)
- [Scripts](./internals/scripts.md)
- [Fork feature ledger](./internals/fork-feature-ledger.md)
- [Connection runtime](./internals/connection-runtime.md)
- [Providers](./internals/providers.md)
- [Remote environments](./internals/remote.md)
Expand Down
59 changes: 59 additions & 0 deletions docs/internals/fork-feature-ledger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Fork feature ledger

The fork feature ledger at `.github/fork-features.yml` records the behavioral capabilities that the
fork deliberately maintains beyond upstream. It complements the commit patch stack and the generated
fork-features issue:

- commits preserve how the implementation changed;
- the generated issue summarizes what users receive;
- the ledger states what behavior maintainers intend to preserve, where upstream changes can affect
it, and which tests provide evidence.

The ledger is capability-oriented. A feature and its later fixes share one entry and one stable ID.
CI validates the file with `vp run --filter @t3tools/scripts ledger:check`.

## Fields

- `id`: stable lower-kebab-case capability ID. Do not rename it when implementation details move.
- `title`: short maintainer-facing capability name.
- `status`: `maintained`, `review-needed`, or `retiring`.
- `prs`: fork pull requests that introduced or materially repaired the capability.
- `invariants`: observable behavior that must remain true after upstream integration.
- `implementation_paths`: fork implementation files whose removal or rename must update the ledger.
- `upstream_paths`: files shared with upstream whose upstream changes should prompt semantic review.
- `tests`: focused evidence for the invariants. These files must continue to exist.
- `upstream.status`: `unassessed`, `tracking`, `partial`, or `equivalent`.
- `upstream.tracking`: upstream issues, pull requests, or commits used for an assessed status.
- `upstream.retire_when`: the condition under which the fork implementation can be removed.

Paths are exact files rather than broad directories or globs. Implementation and test paths must
exist in the fork, making local renames and removals fail validation instead of silently weakening the
ledger. Upstream paths are audited when an entry is added to ensure they exist on the current upstream
base; Fork Nightly disables rename detection when diffing upstream so a moved path is reported as both
the watched deletion and a new addition.

## Workflow

Add a ledger entry when a pull request creates a new maintained divergence. Add follow-up pull request
numbers, invariants, paths, and test evidence to the existing entry when repairing or extending one.
Keep implementation and upstream paths distinct, entries sorted by ID, and values within structured
lists sorted.

During an upstream rebase, review a capability when upstream changes one of its upstream paths, when
its tests need conflict resolution, or when the patch range-diff changes. Record upstream evidence
before changing `upstream.status` from `unassessed`. Fork Nightly compares the old and new upstream
commits and writes warnings plus a workflow summary section for every exact upstream-path overlap.
These warnings identify where judgment is needed; they do not claim that an overlap is a behavioral
conflict. Fork CI validation is blocking, while the Nightly overlap step is deliberately advisory so
a reporting failure cannot prevent an otherwise verified release.

When upstream provides overlapping behavior, mark the entry `review-needed` and compare the ledger's
invariants rather than implementation shape. Retire the fork patch only after upstream satisfies the
documented condition and the resulting stack passes the focused tests. Remove the ledger entry in the
same reviewed change that removes the final fork-owned behavior.

## Coverage

Coverage begins incrementally with cross-surface and frequently conflicted capabilities. The ledger's
`coverage: incremental` marker is an explicit statement that unlisted fork behavior still exists; it
must not be interpreted as a complete inventory until a reviewed backfill says otherwise.
48 changes: 48 additions & 0 deletions scripts/check-fork-feature-ledger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env node
// @effect-diagnostics nodeBuiltinImport:off

import * as NodeFS from "node:fs";
import * as NodePath from "node:path";
import * as NodeURL from "node:url";

import {
findForkFeatureOverlaps,
loadForkFeatureLedger,
renderForkFeatureOverlapSummary,
validateForkFeatureLedger,
} from "./fork-feature-ledger.ts";

const repoRoot = NodePath.resolve(NodePath.dirname(NodeURL.fileURLToPath(import.meta.url)), "..");

try {
const ledger = loadForkFeatureLedger(repoRoot);
const errors = validateForkFeatureLedger(ledger, repoRoot);
if (errors.length > 0) {
process.stderr.write(`${errors.map((error) => `- ${error}`).join("\n")}\n`);
process.exitCode = 1;
} else {
process.stdout.write(`Validated ${ledger.features.length} fork feature ledger entries.\n`);
const changedPathsFlagIndex = process.argv.indexOf("--changed-paths");
if (changedPathsFlagIndex !== -1) {
const changedPathsFile = process.argv[changedPathsFlagIndex + 1];
if (changedPathsFile === undefined) throw new Error("--changed-paths requires a file path.");
const changedPaths = NodeFS.readFileSync(changedPathsFile, "utf8")
.split(/\r?\n/u)
.filter((path) => path.length > 0);
const overlaps = findForkFeatureOverlaps(ledger, changedPaths);
const summary = renderForkFeatureOverlapSummary(overlaps);
process.stdout.write(summary);
if (process.env.GITHUB_STEP_SUMMARY !== undefined) {
NodeFS.appendFileSync(process.env.GITHUB_STEP_SUMMARY, summary);
}
for (const { feature, paths } of overlaps) {
process.stdout.write(
`::warning title=Upstream touched fork feature::${feature.id}: ${paths.join(", ")}\n`,
);
}
}
}
} catch (error) {
process.stderr.write(`${String(error)}\n`);
process.exitCode = 1;
}
Loading
Loading