Skip to content

feat(web): share project actions via t3.json#4363

Open
edoedac0 wants to merge 1 commit into
pingdotgg:mainfrom
edoedac0:feat/share-project-actions
Open

feat(web): share project actions via t3.json#4363
edoedac0 wants to merge 1 commit into
pingdotgg:mainfrom
edoedac0:feat/share-project-actions

Conversation

@edoedac0

@edoedac0 edoedac0 commented Jul 23, 2026

Copy link
Copy Markdown

What Changed

  • add an off-by-default “Share with everyone on this project” switch to the add/edit action dialog
  • create t3.json with the published schema when the first action is shared
  • merge new or edited actions into an existing t3.json without duplicating matching actions or dropping unrelated project settings
  • keep personal keybindings local and show a non-blocking error toast if the local action saves but t3.json cannot be updated
  • cover create, append, edit, invalid-file, forward-compatible-field, and script-limit behavior with focused tests

Why

t3.json already lets teammates import repository-defined actions, but sharing a newly configured action currently requires manually editing JSON. This keeps personal actions local by default while making the existing checked-in sharing workflow available directly from the action form.

This builds on #4317.

UI Changes

Before: Actions created in the dialog were stored only in the local project configuration; sharing required a manual t3.json edit.

After: The same dialog includes an off-by-default sharing switch with explanatory copy. Enabling it writes the saved action to the repository's t3.json.

The requester manually verified the complete add-and-share flow in the live development app.

Verification

  • pnpm --filter @t3tools/web typecheck
  • pnpm exec vp lint apps/web/src/components/ChatView.tsx apps/web/src/components/ProjectScriptsControl.tsx apps/web/src/components/files/projectFilesQueryState.ts apps/web/src/t3ProjectFileScripts.ts apps/web/src/t3ProjectFileScripts.test.ts
  • pnpm exec vp test run apps/web/src/t3ProjectFileScripts.test.ts apps/web/src/components/files/projectFilesQueryState.test.ts (6 tests)
  • targeted formatting check for the five changed files
  • integrated web flow manually verified by the requester

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • No animation or motion behavior was changed, so a video is not applicable

Note

Medium Risk
Opt-in writes to version-controlled t3.json in the workspace; validation and partial-failure handling limit blast radius, but teammates can still pick up unintended shared actions if misused.

Overview
Adds an off-by-default “Share with everyone on this project” switch to the add/edit action dialog so a saved action can be written into the repo’s checked-in t3.json, not only local project config.

After a successful local save, optional sharing reads current t3.json (via new getProjectFileQueryData), merges the action with upsertT3ProjectFileScript (create file, append, or update by name/command; keeps unrelated keys; 50-script cap; invalid JSON errors), then persists through writeProjectFile. Keybindings stay local. If sharing fails, the user gets a non-blocking toast (“Action saved, but could not be shared”).

Includes unit tests for the merge helper.

Reviewed by Cursor Bugbot for commit d98df4f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Share project actions via t3.json when saving or updating scripts

  • Adds a "Share with everyone on this project" toggle to the add/edit action dialog in ProjectScriptsControl, exposing a new shareWithProject flag on NewProjectScriptInput.
  • When shareWithProject is true, ChatViewContent writes or updates the action in the project's t3.json file after saving, using the new upsertT3ProjectFileScript utility in t3ProjectFileScripts.ts.
  • upsertT3ProjectFileScript preserves unrelated t3.json fields, matches existing entries by command or case-insensitive name, enforces a 50-script limit, and returns pretty-printed JSON.
  • On success, the in-memory query cache is updated via setProjectFileQueryData. If writing t3.json fails, the action still saves locally and an error toast is shown.
📊 Macroscope summarized d98df4f. 3 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a9c1321-6ff0-460d-b4ba-31bb71f339be

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 23, 2026
Comment on lines +76 to +83
const scripts = Array.isArray(rawProjectFile.scripts)
? [...rawProjectFile.scripts]
: [...decodedScripts];
if (existingIndex === -1) {
scripts.push(nextFileScript);
} else {
scripts[existingIndex] = nextFileScript;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium src/t3ProjectFileScripts.ts:76

When upsertT3ProjectFileScript updates an existing action, scripts[existingIndex] = nextFileScript replaces the entire raw script object wholesale, silently deleting any forward-compatible or tool-specific fields the user had on that entry. This defeats the function's purpose of preserving unknown raw JSON fields. When rawProjectFile.scripts is the source list and the existing raw entry is an object, spread the existing entry before nextFileScript so extra keys are retained.

  const scripts = Array.isArray(rawProjectFile.scripts)
    ? [...rawProjectFile.scripts]
    : [...decodedScripts];
  if (existingIndex === -1) {
    scripts.push(nextFileScript);
  } else {
-    scripts[existingIndex] = nextFileScript;
+    const existingRaw = scripts[existingIndex];
+    scripts[existingIndex] =
+      existingRaw && typeof existingRaw === "object" && !Array.isArray(existingRaw)
+        ? { ...(existingRaw as Record<string, unknown>), ...nextFileScript }
+        : nextFileScript;
  }
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/t3ProjectFileScripts.ts around lines 76-83:

When `upsertT3ProjectFileScript` updates an existing action, `scripts[existingIndex] = nextFileScript` replaces the entire raw script object wholesale, silently deleting any forward-compatible or tool-specific fields the user had on that entry. This defeats the function's purpose of preserving unknown raw JSON fields. When `rawProjectFile.scripts` is the source list and the existing raw entry is an object, spread the existing entry before `nextFileScript` so extra keys are retained.

Comment on lines +67 to +71
const existingIndex = decodedScripts.findIndex(
(fileScript) =>
(input.previousScript !== undefined && isSameScript(fileScript, input.previousScript)) ||
isSameScript(fileScript, input.script),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High src/t3ProjectFileScripts.ts:67

upsertT3ProjectFileScript can overwrite the wrong action when editing a shared action. If the action being edited appears later in scripts but an earlier unrelated action already shares the new name or command, findIndex matches the earlier action and overwrites it, leaving the original edited action stale in the file. The previousScript match is not prioritized over the new-name match because both checks run in the same findIndex pass. Search for a previousScript match first in a separate pass, and only fall back to matching by the new name or command if no previous match is found.

Suggested change
const existingIndex = decodedScripts.findIndex(
(fileScript) =>
(input.previousScript !== undefined && isSameScript(fileScript, input.previousScript)) ||
isSameScript(fileScript, input.script),
);
const matchPrevious = (fileScript: T3ProjectFileScript) =>
input.previousScript !== undefined && isSameScript(fileScript, input.previousScript);
const matchCurrent = (fileScript: T3ProjectFileScript) =>
isSameScript(fileScript, input.script);
const previousIndex = decodedScripts.findIndex(matchPrevious);
const existingIndex =
previousIndex !== -1
? previousIndex
: decodedScripts.findIndex(matchCurrent);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/t3ProjectFileScripts.ts around lines 67-71:

`upsertT3ProjectFileScript` can overwrite the wrong action when editing a shared action. If the action being edited appears later in `scripts` but an earlier unrelated action already shares the new name or command, `findIndex` matches the earlier action and overwrites it, leaving the original edited action stale in the file. The `previousScript` match is not prioritized over the new-name match because both checks run in the same `findIndex` pass. Search for a `previousScript` match first in a separate pass, and only fall back to matching by the new name or command if no previous match is found.

return appAtomRegistry.get(optimisticFileAtom(environmentId, cwd, relativePath))?.data ?? null;
}

export function getProjectFileQueryData(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High files/projectFilesQueryState.ts:71

getProjectFileQueryData returns null whenever the AsyncResult has no value — including while the read is still in-flight or after a transient read failure. Callers treat null as "file does not exist" and proceed to create fresh contents and write them, so an existing t3.json can be overwritten and project settings/actions lost if the user saves before the initial read completes or after a transient read failure. The function conflates "no value yet" with "file does not exist" because it only checks Option.getOrNull(AsyncResult.value(result)) and ignores the AsyncResult state. Consider distinguishing pending/failed states from a confirmed empty result so callers don't write over a file they haven't successfully read.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/files/projectFilesQueryState.ts around line 71:

`getProjectFileQueryData` returns `null` whenever the `AsyncResult` has no value — including while the read is still in-flight or after a transient read failure. Callers treat `null` as "file does not exist" and proceed to create fresh contents and write them, so an existing `t3.json` can be overwritten and project settings/actions lost if the user saves before the initial read completes or after a transient read failure. The function conflates "no value yet" with "file does not exist" because it only checks `Option.getOrNull(AsyncResult.value(result))` and ignores the `AsyncResult` state. Consider distinguishing pending/failed states from a confirmed empty result so callers don't write over a file they haven't successfully read.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d98df4f. Configure here.

contents: existingContents,
script: input.script,
...(input.previousScript ? { previousScript: input.previousScript } : {}),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Share skips unread t3.json

High Severity

Sharing a project script can overwrite the t3.json file. This happens because getProjectFileQueryData returns null if the file read query is pending or failed. upsertT3ProjectFileScript then interprets this null as an empty file, leading to a new, minimal t3.json being written that discards existing shared actions and settings.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d98df4f. Configure here.

(fileScript) =>
(input.previousScript !== undefined && isSameScript(fileScript, input.previousScript)) ||
isSameScript(fileScript, input.script),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Edited share hits wrong row

High Severity

The findIndex logic in upsertT3ProjectFileScript can incorrectly identify the script to update. When matching, isSameScript broadly checks for either name or command equality. This can lead to an unrelated, earlier script being overwritten if it matches the new name or command, leaving the intended script stale and corrupting t3.json.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d98df4f. Configure here.

@macroscopeapp

macroscopeapp Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

3 blocking correctness issues found. New feature introducing project action sharing via t3.json. Multiple high-severity unresolved review comments identify potential data loss bugs: file reads returning null during pending state could overwrite existing t3.json, and the script matching logic could update the wrong action.

You can customize Macroscope's approvability policy. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant