Skip to content

Maxy/feat use pimemory in projects - #21

Closed
maxy-shpfy wants to merge 1930 commits into
Cloud-Pipelines:masterfrom
TangleML:maxy/feat--use-pimemory-in-projects
Closed

maxy-shpfy wants to merge 1930 commits into
Cloud-Pipelines:masterfrom
TangleML:maxy/feat--use-pimemory-in-projects

Conversation

@maxy-shpfy

Copy link
Copy Markdown

No description provided.

dependabot Bot and others added 30 commits July 24, 2026 21:19
Bumps the npm-minor-patch group with 1 update: [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip).


Updates `knip` from 6.27.0 to 6.29.0
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.29.0/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 6.29.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.2.0 to 4.3.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](nodeca/js-yaml@4.2.0...4.3.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.3.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Description

Floating windows that were saved on a larger screen could be restored in an off-screen position, making them unreachable. This adds a viewport clamping step that snaps persisted floating windows back into the visible area when they are restored.

A new `viewportUtils.ts` module centralises the shared logic:

- `ViewportBounds` — a common interface replacing the local `Bounds` type that existed in `usePropertiesWindowPositioning.ts`.
- `bringIntoViewport` — repositions a window to the nearest in-bounds edge, replacing the local `clampPosition` function and accounting for the window chrome height so the title bar always remains reachable.
- `isFullyWithinViewport` — checks whether a window already fits within the usable area.
- `getFloatingViewportBounds` — computes the usable region (below the top nav, between the left/right dock areas) from live `window` dimensions, taking into account whether dock areas are empty or collapsed.

`WINDOW_CHROME_HEIGHT` is promoted to the shared `types.ts` constants file so it can be referenced from `viewportUtils` without duplication.

The clamping is applied only to persisted floating windows (`dockState === "none"` and `persisted !== null`). Docked windows and first-visit default positions are left untouched.

## Screenshots (if applicable)

[Screen Recording 2026-07-27 at 12.11.23 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.com/user-attachments/thumbnails/0376e6b0-f38a-4386-a7e9-86d8ed9d852d.mov" />](https://app.graphite.com/user-attachments/video/0376e6b0-f38a-4386-a7e9-86d8ed9d852d.mov)



## Test Instructions

1. Open the application and arrange several floating windows, then save/close.
2. Reduce the browser window size (or simulate a smaller viewport) so that the saved positions would be off-screen.
3. Reload the application and verify that all floating windows snap back into the visible area with their title bars accessible.
4. Verify that docked windows are unaffected by the clamping logic.
5. Verify that windows opened for the first time (no persisted state) appear at their configured default positions.

## Additional Comments

Unit tests covering `bringIntoViewport`, `isFullyWithinViewport`, and `getFloatingViewportBounds` are included in `viewportUtils.test.ts`. Tests for the viewport clamping behaviour within `buildWindowModelInit` are added to `windowStore.utils.test.ts`.
…2579)

Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 6.9.1 to 7.0.0.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](testing-library/jest-dom@v6.9.1...v7.0.0)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-version: 7.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Description

Displays the task argument value on input `IONode` cards when execution data is available. The argument value takes precedence over the default value, falling back to the default value if no argument value is present, and then to "No value" if neither exists.

This is achieved by reading `task_spec.arguments` from the optional execution data context and resolving the relevant argument value by input name using the `getArgumentValue` utility.

## Screenshots (if applicable)

| Before | After |
| --- | --- |
| ![image.png](https://app.graphite.com/user-attachments/assets/54bacde6-cf66-44e7-a318-e23fcd451762.png)<br> | ![image.png](https://app.graphite.com/user-attachments/assets/6ef71363-c8fc-4aa6-b709-78d6111d5e13.png)<br> |

## Test Instructions

1. Open a pipeline that has a run/execution with task arguments defined.
2. Navigate to the visual editor for that execution.
3. Inspect an input `IONode` card — it should display the argument value passed to the task rather than the default value.
4. Verify that input nodes without a matching argument still fall back to the default value or "No value".
5. Verify that output nodes are unaffected and continue to display the connected value or "No value".

## Additional Comments
## Description

Improves the visual styling of run tool buttons in the Run View. Row tool buttons now have a visible border by default, and the Cancel Run button has been updated with a more polished destructive style — using a transparent background, a muted destructive border, and a subtle red hover background instead of relying solely on text color to indicate the destructive action.

## Screenshots (if applicable)

| Before | After |
| --- | --- |
| ![image.png](https://app.graphite.com/user-attachments/assets/c15a1ecc-0d77-4ae5-a113-4693c127652e.png)<br> | ![image.png](https://app.graphite.com/user-attachments/assets/e993a049-ed62-4e85-b075-626866058d5b.png)<br> |

## Test Instructions

1. Navigate to the Run View page.
2. Verify that row tool buttons display a visible border in their default state.
3. Verify that the Cancel Run button displays with a transparent background and a muted destructive-colored border.
4. Hover over the Cancel Run button and confirm a subtle red background appears.

## Additional Comments
## Description

Adds `readOnly` support to `MultilineTextInputDialog` and `AutoGrowTextarea`, allowing these components to be used for displaying values without allowing edits. When in read-only mode, the dialog replaces the Cancel/Confirm button pair with a single Close button, and the textarea displays a copy button in place of (or alongside) the expand button.

The `RunViewInputDetails` component now uses `AutoGrowTextarea` in read-only mode to display argument values and default values, replacing the previous `CopyText` elements. This gives users syntax highlighting, auto-growing display, copy functionality, and an expand dialog for long values.

`AutoGrowTextarea` and `CopyValueButton` have been moved from Editor-specific paths to `v2/shared/components` so they can be reused across Editor and RunView contexts.

## Screenshots (if applicable)

[Screen Recording 2026-07-27 at 7.17.27 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.com/user-attachments/thumbnails/0ca1685e-2b7b-4683-9c0f-3df4a6ef60ca.mov" />](https://app.graphite.com/user-attachments/video/0ca1685e-2b7b-4683-9c0f-3df4a6ef60ca.mov)



## Test Instructions

1. Open a run in RunView and inspect an input node's context panel.
2. Verify that argument values and default values are displayed in the auto-growing, read-only textarea with syntax highlighting.
3. Hover over the textarea to confirm the copy and expand buttons appear.
4. Click the expand button and verify the dialog opens in read-only mode with only a Close button (no Confirm button).
5. Verify that existing editable `AutoGrowTextarea` fields in the Editor still function correctly with Cancel/Confirm buttons.

## Additional Comments

`CopyValueButton` was previously nested under `InputLabel/components`. Moving it to `v2/shared/components` makes it accessible without creating cross-route import dependencies.
## What this PR does

This upgrades our automated **gardening** skill — the bot that periodically sweeps the codebase for cleanup opportunities and opens draft PRs for review — in two significant ways. It touches only the skill's own instructions and config; no application or product code changes.

### 1. A new "consistency" pillar

Until now the bot could only find problems that matched a fixed checklist of known line-level patterns (e.g. "swap a raw `<p>` for our `Paragraph` primitive"). It was structurally blind to a whole category of issue: places where the codebase does the **same conceptual thing two different ways**.

The motivating example: most of the app wraps async/loading UI in our shared `SuspenseWrapper`, but the v2 editor hand-rolls its own Suspense + fallback instead. Catching that requires comparing files *against each other*, not against a catalog — something none of the existing pillars could do.

The new pillar builds an index of "how do we do X across the **whole** codebase," identifies the canonical approach, and flags the outliers so they can be converged onto it. To keep it from inventing conventions or enforcing personal taste, it only auto-fixes things that are **either** written down in a human-curated registry of canonical patterns **or** already an overwhelming supermajority of sites — anything else it merely *proposes* for a human to bless first. It also deliberately ignores recency and scans the entire tree, so a five-year-old outlier is exactly what it's built to find.

### 2. A rethink of what happens to findings the bot doesn't fix immediately

Previously, everything the bot couldn't ship right away was dumped into a single GitHub "backlog" issue that nothing ever read again — a graveyard that only grew. This PR replaces that so every finding now has one clear fate:

- **Ship now** — safe, proven, behavior-preserving → goes into a draft PR.
- **Carry over** — safe and ready, but didn't fit this run's PR/file budget → the bot itself picks it up **first** on the next run, so it actually gets done.
- **Decision** — a real issue the bot can't safely fix alone → a short, curated yes/no list for a human, each with a change sketch and what saying "yes" unlocks.

Low-confidence guesses, coverage gaps, and gate failures no longer masquerade as "work" in an issue — they just show up in the run summary.

## Net effect

The bot now examines the whole codebase, can spot cross-file inconsistencies it was previously blind to, and nothing safe ever silently rots in a forgotten ticket.
## Description

Introduces a distinct `value` field for pipeline inputs that sits above `defaultValue` in the resolution hierarchy. When a user sets a value in the Input Details panel, it now calls `setInputValue`, which sets both `value` and `defaultValue` on the input simultaneously. The displayed label has been renamed from "Default Value" to "Value", and the resolved display uses `value ?? defaultValue` so that an explicitly set value takes precedence. A "Use as default" indicator has also been added to the input label area.

## Screenshots (if applicable)

[Screen Recording 2026-07-29 at 12.40.03 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.com/user-attachments/thumbnails/e1048b36-ac2e-443f-8e9d-25146aae736b.mov" />](https://app.graphite.com/user-attachments/video/e1048b36-ac2e-443f-8e9d-25146aae736b.mov)



## Test Instructions

1. Open a pipeline in the editor and select an input node.
2. In the Input Details panel, confirm the field is now labelled "Value" with a "Use as default" indicator.
3. Enter a value and confirm it is reflected on the node card, taking precedence over any existing default value.
4. Confirm undo/redo correctly reverts the value change.
5. Confirm that pipelines with existing default values continue to display correctly when no explicit value is set.

## Additional Comments
## Description

The `MissingPipelineInputValueResolution` component has been updated to set a pipeline input's **value** directly rather than its **default value**. The effective value displayed and edited is now derived from `input.value ?? input.defaultValue`, prioritizing an explicitly set value over the default. The label and UI copy have been updated from "Default value" to "Value", and a "Use as default" indicator (with a checkbox icon) has been added to the label row to communicate intent to the user.

## Screenshots (if applicable)

![image.png](https://app.graphite.com/user-attachments/assets/e5e6238f-1b30-4c24-b4d4-dae94cdd86d6.png)



## Test Instructions

1. Open a pipeline in the editor that has a missing pipeline input value issue.
2. Verify the resolution panel displays "Set a value for pipeline input" with a "Value" label and a "Use as default" indicator.
3. Enter a value and confirm it is saved as the input's value (not the default value).
4. Verify that if an explicit value already exists, it takes precedence over the default value in the text area.

## Additional Comments

The `ioActions.setInputValue` call replaces the previous `ioActions.setInputDefaultValue` call, so any downstream handling of input values vs. default values should be verified to ensure compatibility.
## Description

A simple improvement for Loading Screens where a button to return home will be provided after the loading has been going for 8+ seconds (or any other given delay). This is provided primarily because there is no other app-native way to leave the loading screen.

This PR also makes the v2 editor suspense use the loading screen instead of it's own bespoke component.

<!-- Please provide a brief description of the changes made in this pull request. Include any relevant context or reasoning for the changes. -->

## Related Issue and Pull requests

<!-- Link to any related issues using the format #<issue-number> -->

## Type of Change

- [x] Improvement

## Checklist

<!-- Please ensure the following are completed before submitting the PR -->

- [ ] I have tested this does not break current pipelines / runs functionality
- [ ] I have tested the changes on staging

## Screenshots (if applicable)

![image.png](https://app.graphite.com/user-attachments/assets/0a348400-8d62-44a8-aa70-a2f1e21fdfa1.png)

<!-- Include any screenshots that might help explain the changes or provide visual context -->

## Test Instructions

Load a large pipeline or anything else to make it take more than 8 seconds to load the pipeline. Confirm that the text and button show up and work.

<!-- Detail steps and prerequisites for testing the changes in this PR -->

## Additional Comments

<!-- Add any additional context or information that reviewers might need to know regarding this PR -->
## Description

An attempt to cut down on excessive long-winded narration-style code comments that the AI has recently been producing. This appears to have been caused by an unbounded adherence to JSDoc principles. This PRs loosens that connection and gives the AI some more detailed instructions around when and why a comment should be added in the code.

<!-- Please provide a brief description of the changes made in this pull request. Include any relevant context or reasoning for the changes. -->

## Related Issue and Pull requests

<!-- Link to any related issues using the format #<issue-number> -->

## Type of Change

<!-- Please delete options that are not relevant -->

- [ ] Bug fix
- [ ] New feature
- [x] Improvement
- [ ] Cleanup/Refactor
- [ ] Breaking change
- [ ] Documentation update

## Checklist

<!-- Please ensure the following are completed before submitting the PR -->

- [ ] I have tested this does not break current pipelines / runs functionality
- [ ] I have tested the changes on staging

## Screenshots (if applicable)

<!-- Include any screenshots that might help explain the changes or provide visual context -->

## Test Instructions

<!-- Detail steps and prerequisites for testing the changes in this PR -->

## Additional Comments

<!-- Add any additional context or information that reviewers might need to know regarding this PR -->
…0.0 (#2573)

Bumps [eslint-plugin-simple-import-sort](https://github.com/lydell/eslint-plugin-simple-import-sort) from 13.0.0 to 14.0.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/lydell/eslint-plugin-simple-import-sort/blob/main/CHANGELOG.md">eslint-plugin-simple-import-sort's changelog</a>.</em></p>
<blockquote>
<h3>Version 14.0.0 (2026-07-16)</h3>
<p>ES2022 allows string literals as module export names (&quot;arbitrary module namespace names&quot;):</p>
<pre lang="js"><code>export { yukuTs as &quot;yuku-ts&quot; };
import { &quot;a-b&quot; as c } from &quot;a&quot;;
</code></pre>
<p>This release adds support for such quotes names. Previously, those were sorted oddly, and the autofix could suggest changes that wasn’t valid syntax.</p>
<p>This is only a breaking change if you use string literals as module export names, and only in the form of that you need to autofix your files.</p>
<p>Thanks to Kamronbek_Juraev (<a href="https://github.com/KAMRONBEK"><code>@​KAMRONBEK</code></a>) for fixing this!</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/lydell/eslint-plugin-simple-import-sort/commit/bef18bd5de7a404efe4ba123d55715258df7cd78"><code>bef18bd</code></a> eslint-plugin-simple-import-sort v14.0.0</li>
<li><a href="https://github.com/lydell/eslint-plugin-simple-import-sort/commit/d53d160856a2249ef85d3e8409493302a649872f"><code>d53d160</code></a> Fix autofix and sort order for quoted names (<a href="https://redirect.github.com/lydell/eslint-plugin-simple-import-sort/issues/217">#217</a>)</li>
<li><a href="https://github.com/lydell/eslint-plugin-simple-import-sort/commit/6063737734238b0c2cfd5b20745ddbdb5aef41bb"><code>6063737</code></a> Bump js-yaml from 4.1.1 to 4.3.0 (<a href="https://redirect.github.com/lydell/eslint-plugin-simple-import-sort/issues/214">#214</a>)</li>
<li><a href="https://github.com/lydell/eslint-plugin-simple-import-sort/commit/143b70e47c63f47bb4887c616d9eb6077d85301d"><code>143b70e</code></a> Bump vitest and <code>@​vitest/coverage-v8</code> (<a href="https://redirect.github.com/lydell/eslint-plugin-simple-import-sort/issues/212">#212</a>)</li>
<li><a href="https://github.com/lydell/eslint-plugin-simple-import-sort/commit/0f5927612931ef1018b83b79521046c7f323c6ed"><code>0f59276</code></a> Bump vite from 7.1.11 to 7.3.2 (<a href="https://redirect.github.com/lydell/eslint-plugin-simple-import-sort/issues/211">#211</a>)</li>
<li><a href="https://github.com/lydell/eslint-plugin-simple-import-sort/commit/24d74cdf63859920deca1adc1c7c889ce0c32b96"><code>24d74cd</code></a> Bump lodash from 4.17.23 to 4.18.1 (<a href="https://redirect.github.com/lydell/eslint-plugin-simple-import-sort/issues/210">#210</a>)</li>
<li><a href="https://github.com/lydell/eslint-plugin-simple-import-sort/commit/89746062d276df7448ec12a3ad74a784ad430053"><code>8974606</code></a> Update GitHub Actions (<a href="https://redirect.github.com/lydell/eslint-plugin-simple-import-sort/issues/209">#209</a>)</li>
<li>See full diff in <a href="https://github.com/lydell/eslint-plugin-simple-import-sort/compare/v13.0.0...v14.0.0">compare view</a></li>
</ul>
</details>
<br />
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.3.0 to 4.3.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.1/CHANGELOG.md)
- [Commits](nodeca/js-yaml@4.3.0...4.3.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.3.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Description

Adds task annotation display to the Run View task details panel. Annotations are rendered inside the existing details card as a collapsible "Task Annotations" section, showing each annotation's key and value in a monospace font with copy-to-clipboard support. Empty keys or values are indicated with placeholder text rather than being silently omitted.

To support styled rendering of annotation text, `CopyText` now accepts `tone` and `font` props, which are forwarded directly to the underlying `Text` component.

## Screenshots (if applicable)

![image.png](https://app.graphite.com/user-attachments/assets/ecdce5f1-f536-4d9a-884a-4d1e9a519c36.png)



## Test Instructions

1. Open a run that has tasks with annotations attached.
2. Navigate to a task node and open its details panel.
3. Expand the "Task Annotations" section and verify each annotation's key and value are displayed correctly.
4. Click the copy button on a key or value and confirm the correct text is copied to the clipboard.
5. Verify that tasks with empty annotation keys or values show the appropriate placeholder text.

## Additional Comments
## What this does

Improves how Parquet files are previewed in the artifact viewer.

**Before:** opening a Parquet preview downloaded the whole file and then showed up to 1,000 rows. Big files were slow, and files above the preview size limit couldn't be opened at all.

**Now:** the viewer reads the file in pages — it only pulls the parts it actually needs (the file's metadata plus the first rows) instead of downloading everything up front. In practice this means:

- **Large files open quickly, at any size.** Because we no longer download the whole file, Parquet previews are no longer blocked by the size limit that applies to other artifact types.
- **You get a fast preview of the first rows** of data as soon as it opens.
- **The header shows the total row and column counts** read straight from the file.
- **A "Download schema" button** lets you save the file's column layout (names, types, nullability) as a JSON file.

## Type of Change

- [x] Improvement

## Test Instructions

1. Open a run that has a Parquet output artifact.
2. Select the task (or output) and open its artifact preview.
3. Confirm you see: the data preview table, the total row/column counts in the header, and a working **Download schema** button.
4. Try a large Parquet file and confirm it opens quickly — previously a file this size may have been blocked as too large to preview.

## Notes

Also includes some internal cleanup raised in review: Parquet-only code was moved into its own module so non-Parquet previews (CSV/TSV) stay lightweight, and the fetch/error-handling logic that was duplicated is now shared.

## Follow-up

Additional viewer affordances — incremental **Load more / Load max**, a **Download full dataset** escape hatch, and CSV/TSV row/column-count parity — are stacked on top in #2593.
## Description

Follow-up to #2557. This PR adds extra viewer affordances for both Parquet and CSV/TSV.

**1\. Load more / Load max (Parquet).** The base PR previews the top 100 rows and stops. Here the viewer can pull additional pages on demand via range reads — **Load more** fetches the next batch, **Load max** fills up to the preview cap. Only the newly requested range is fetched each time; rows already loaded are never re-read, and the whole file is never downloaded.

**2\. Preview limit is a cell budget, not a row count.** The preview table renders every cell into the DOM (no virtualization), so its cost scales with **rows × columns**, not rows alone. A flat 1,000-row cap therefore over-protects narrow tables and under-protects wide ones. Instead, the preview is bounded by a **50,000-cell budget**: the row limit is `floor(50,000 / columnCount)`, with an absolute backstop of **10,000 rows** so a very narrow table still can't flood the DOM. This adapts to table shape:

| Columns | Preview row limit |
| --- | --- |
| ≤ 5 | 10,000 (backstop) |
| 10 | 5,000 |
| 20 | 2,500 |
| 100 | 500 |

Rows are atomic: the budget is floored to whole rows up front, so the rendered cell count never exceeds 50,000 and no partial rows are shown. The same limit applies to both Parquet (**Load max**) and CSV/TSV previews.

**3\. Download full dataset.** When the preview limit is reached but the file still has more rows than are shown, the footer surfaces a **Download full dataset** link so the user has a clear escape hatch to the complete data (opens the signed URL for remote artifacts).

**4\. CSV/TSV parity.** CSV/TSV previews now report the **exact** total row count and column count in the header, matching Parquet. Counting is done with a streaming parse (every row is counted, only the preview rows are retained). Remote CSVs get a **Download full dataset** link to the signed URL; inline CSV values trigger a direct file download.



![image.png](https://app.graphite.com/user-attachments/assets/edeca1ee-c59d-433c-9a34-fe9cf84c43f8.png)



## Related Issue and Pull requests

- Stacked on #2557

## Type of Change

- [x] Improvement

## Checklist

- [x] I have tested this does not break current pipelines / runs functionality
- [ ] I have tested the changes on staging

## Test Instructions

1. Open a run with a **Parquet** artifact that has more than 100 rows and open its preview.
2. Confirm a top-100 preview loads, then use **Load more** / **Load max** and confirm additional rows load without a full-file download.
3. Confirm the preview stops at the cell budget: for a **wide** table (e.g. 50+ columns) **Load max** stops well below 10,000 rows, and once the limit is reached a **Download full dataset** link appears.
4. Open a **CSV/TSV** artifact and confirm the header shows exact row/column counts, and that the download affordance works for both remote and inline CSV.

## Additional Comments
## Description

Adds artifact previews to Output Node Properties in v2 Run View, as per v1 run view. The two versions now have parity for artifact preview offerings.



Also adds a test file for RunViewOutputDetails.

<!-- Please provide a brief description of the changes made in this pull request. Include any relevant context or reasoning for the changes. -->

## Related Issue and Pull requests

<!-- Link to any related issues using the format #<issue-number> -->

## Type of Change

- [x] Improvement

## Checklist

<!-- Please ensure the following are completed before submitting the PR -->

- [ ] I have tested this does not break current pipelines / runs functionality
- [ ] I have tested the changes on staging

## Screenshots (if applicable)

![image.png](https://app.graphite.com/user-attachments/assets/4665971a-6e34-4f48-834d-2f4b9d39f65c.png)



<!-- Include any screenshots that might help explain the changes or provide visual context -->

## Test Instructions

Run a pipeline that outputs tabulated data to an output node.  
Confirm in the v2 run view that the artifact preview appears when the output node is seletced and the run is complete.



Note that artifact previews do not work locally, so if you actually try to preview the data it will not load. This is a known limitation. Try in staging/tophat!

<!-- Detail steps and prerequisites for testing the changes in this PR -->

## Additional Comments

<!-- Add any additional context or information that reviewers might need to know regarding this PR -->
…on (#2607)

## Description

Task annotations in the Run View are now split into two distinct sections: **Task Annotations** (user-defined) and **System Annotations** (system-defined, based on the `SYSTEM_ANNOTATIONS` list). User annotations are shown expanded by default, while system annotations are collapsed. Either section is only rendered if it contains at least one annotation.

## Screenshots (if applicable)

| Before | After |
| --- | --- |
| ![image.png](https://app.graphite.com/user-attachments/assets/47a37a7e-14eb-4c46-85bc-42ca44a27f96.png)<br> | ![image.png](https://app.graphite.com/user-attachments/assets/a163efec-311f-42e8-979e-4df8287ea24f.png)<br> |

## Test Instructions

1. Open a run in the Run View that has task nodes with annotations.
2. Verify that user-defined annotations appear under the **Task Annotations** section.
3. Verify that system-defined annotations (keys matching `SYSTEM_ANNOTATIONS`) appear under the **System annotations** section, collapsed by default.
4. Verify that if only one type of annotation exists, only the relevant section is rendered.

## Additional Comments

UX is consistent with the Editor counter part
## Description

Moves the `raw data`, `copy uri` and `share`buttons onto a new line so they no longer conflict with the dialog's `close` and `maximise` buttons.

<!-- Please provide a brief description of the changes made in this pull request. Include any relevant context or reasoning for the changes. -->

## Related Issue and Pull requests

<!-- Link to any related issues using the format #<issue-number> -->

## Type of Change

<!-- Please delete options that are not relevant -->

- [x] Bug fix
- [x] Improvement

## Checklist

<!-- Please ensure the following are completed before submitting the PR -->

- [ ] I have tested this does not break current pipelines / runs functionality
- [ ] I have tested the changes on staging

## Screenshots (if applicable)

before

![image.png](https://app.graphite.com/user-attachments/assets/2616b02e-ea71-4215-96b7-752960284d9d.png)

after

![image.png](https://app.graphite.com/user-attachments/assets/06f1dac2-42b6-4fa7-8669-0ad1520ad706.png)



<!-- Include any screenshots that might help explain the changes or provide visual context -->

## Test Instructions

<!-- Detail steps and prerequisites for testing the changes in this PR -->

## Additional Comments

<!-- Add any additional context or information that reviewers might need to know regarding this PR -->
## Description

Fixes unintended hardcoded `text-black` and `text-white`

<!-- Please provide a brief description of the changes made in this pull request. Include any relevant context or reasoning for the changes. -->

## Related Issue and Pull requests

<!-- Link to any related issues using the format #<issue-number> -->

## Type of Change

<!-- Please delete options that are not relevant -->

- [x] Bug fix

## Checklist

<!-- Please ensure the following are completed before submitting the PR -->

- [ ] I have tested this does not break current pipelines / runs functionality
- [ ] I have tested the changes on staging

## Screenshots (if applicable)

Fixes stuff like this:

![image.png](https://app.graphite.com/user-attachments/assets/18c16242-bcf2-4465-b6b3-1a69a6c7b14f.png)





<!-- Include any screenshots that might help explain the changes or provide visual context -->

## Test Instructions

<!-- Detail steps and prerequisites for testing the changes in this PR -->

## Additional Comments

<!-- Add any additional context or information that reviewers might need to know regarding this PR -->
Actions **B17** in the gardening queue (#2626): blesses `Icon` as the canonical way to render an icon, so
the `consistency` pillar is allowed to migrate the stragglers instead of only proposing.

Two files, docs only. **No `src` changes** — the migration itself is a later, reviewable PR.

## Why it needed a human

The empirical bar was already met — **222** files import `Icon`, **30** still import `lucide-react`
directly (88.1%, over `supermajorityRatio: 0.75`), and **10 of the 30 do both**, which reads as drift
rather than intent. But `pillars/consistency.md` forbids migrating on measurement alone: without a
registry entry that would be the bot inventing a convention and enforcing it repo-wide. Hence the entry.

## Non-Lucide icons are exceptions, not stragglers

`Icon`'s `name` is `keyof typeof icons` from `lucide-react`, so it **structurally cannot** render anything
else. Recorded as KEEP so no future run "fixes" them:

- **`react-icons` brand/language logos** — `FaPython` ×2, `SiGnubash`, `SiRuby`, `TbBrandJavascript`,
  `FaGoogleDrive` across 3 files. Lucide dropped brand logos; there is no equivalent to migrate to.
- **`src/components/ui/**`** — 9 files. shadcn primitives (`dialog`, `select`, `sheet`, `command`,
  `calendar`, `checkbox`, `breadcrumb`, `date-picker`) import their own glyphs and are CLI-regenerable, so
  a hand edit is overwritten by the next `shadcn add`. `icon.tsx` *is* the primitive.
- **Raw `<svg>` that isn't an icon** — `<defs>`/`<marker>` arrowheads in `FlowCanvas/Edges/*`, the
  `Spinner` primitive's own markup, and the bespoke resize grip in `FloatingWindow.tsx`.

## The swap is not size-neutral, so the entry is not blanket `apply: yes`

Worth reading before approving, because it contradicts the "mechanical swap" framing in #2623:

`iconVariants` emits `!w-4 !h-4`. Leading-`!` **still compiles to `!important`** in Tailwind v4.3.3
(verified by compiling `!w-4` with the project's own Tailwind: `.\!w-4 { width: …; !important }`), and
`twMerge@3.6.0` does **not** dedupe `!w-4` against a later `size-5` — it emits both, so `!important` wins.
A blind `<ChevronRight className="size-5" />` → `<Icon name="ChevronRight" className="size-5" />` renders
**16px instead of 20px**.

Across the 30 files there are **59** Lucide JSX usages:

| Bucket | Count | Disposition |
| --- | --- | --- |
| Size class on the scale (`size-4`, `w-5 h-5`, …) | 37 | `apply: yes` — size moves to the `size` prop |
| **No** size class | 9 | `apply: yes` **as `size="xl"`** — Lucide's own default is `width: 24` (`lucide-react.js:39`), so `size="md"` would shrink them |
| Off-scale (`w-2 h-2` ×6, `h-8 w-8`, `w-12 h-12`) | 8 | `apply: flag` — no variant matches and `className` can't beat `!important` |
| Computed `className={cn(…)}` | 5 | `apply: flag` — size not statically readable |

So ~46 of 59 usages are provably size-identical; the other 13 need a human. The off-scale ones are mostly
`StatusIndicator.tsx` at `w-2 h-2` (8px, below `xs`) — if you'd rather they migrate too, the `Icon` scale
needs a step, which is a primitive change and deliberately not in this PR.

## Also fixes a claim I got wrong

#2623 said there was "no `canon-registry.md` entry **and** no convention-skill rule naming `Icon` as
canonical." The registry entry was indeed missing; the convention rule was not — `ui-primitives#icons`
already said "Use `Icon` from `@/components/ui/icon`". That line was one sentence with no `size` guidance,
which is how the `!important` interaction went unnoticed, so this PR fills it in: the size scale, the
warning that a size `className` loses to the variant, and the `react-icons` escape hatch.

## Checks

`prettier --check` passes on both files. No `src`, test, or config changes, so `lint`/`typecheck`/`test`
are unaffected — I did not run the full `validate:test` gate because its `fix` step rewrites the working
tree, and there is no code here for it to validate.
…ta is provably zero (18 swaps) (#2624)

> 🤖 **Automated draft PR — opened by the `gardening` skill. Nothing here auto-merges.**
> 18 swaps in 14 files, every one **provably identical computed CSS**. The `primitives` category is
> `requiresVisualReview: true`, so this PR deliberately applies *only* exact mappings and leaves every
> judgement call to the decision queue. PR #2569's lesson — a `<p>` → `<Paragraph>` swap is **not**
> automatically null — is honoured by proving the class delta from source before touching anything.

## The proof this run is built on

Read from source, not assumed:

1. **`textVariants` fills in defaults.** `src/components/ui/typography.tsx:21` — `Text` defaults to
   `tone="inherit" size="md" weight="regular" font="default"`, which emit
   `text-foreground` / `text-md` / `font-regular` / `""`.
2. **`text-md` and `font-regular` emit no CSS.** Neither `--text-md` nor `--font-weight-regular` exists
   in the `@theme inline` block (`src/styles/global.css:194`), and neither is a Tailwind default
   (v4 ships `text-base`, `font-normal`). `tailwind.config.js` extends `fontSize` with only `2xs`/`3xs`.
   So the *only* class a primitive adds with real CSS behind it is the `tone` colour.
3. **`cn` is `twMerge(clsx(...))`** (`src/lib/utils.ts:4`), so a trailing `className="text-muted-foreground"`
   already **overrode** the `text-foreground` that `tone="inherit"` injects. Moving that colour into
   `tone="subdued"` therefore produces the identical computed colour *and* removes a conflicting
   class pair.

That is what makes each swap below an exact mapping rather than a hopeful one.

## Findings applied

**Raw HTML → primitive (3)** — `ui-primitives#typography`

- [ ] `ConfirmationDialogs/shared.tsx:4` — `<p className="text-muted-foreground">` →
      `<Paragraph tone="subdued">`. Renders `<p>` either way; classes identical.
- [ ] `PipelineRow.tsx:218` — `<span className="text-xs text-muted-foreground">` →
      `<Text size="xs" tone="subdued">`. `Text` renders a `<span>` by default.
- [ ] `CodeViewer.tsx:63` — `<span className="text-sm text-muted-foreground">` →
      `<Text size="sm" tone="subdued">`.

**Hardcoded colour className → `tone` (12)** — `ui-primitives#typography`

- [ ] `DocsQuickLinks.tsx:81`, `InputDetails.tsx:132`, `InputDetails.tsx:155`,
      `MissingPipelineInputValueResolution.tsx:79`, `DashboardFavoritesView.tsx:151`,
      `DashboardRecentlyViewedView.tsx:23`, `DashboardRecentlyViewedView.tsx:100` —
      `className="text-muted-foreground"` → `tone="subdued"`
- [ ] `NodeListItem.tsx:67` — `className="text-destructive"` → `tone="critical"`
- [ ] `DashboardHomeView.tsx:65`, `:147` — → `tone="subdued" className="shrink-0"`
- [ ] `DashboardComponentsView.tsx:71` — → `tone="subdued" className="tabular-nums"`
- [ ] `DashboardComponentsView.tsx:153` — → `tone="subdued" className="font-medium truncate"`

Non-mappable classes (`shrink-0`, `tabular-nums`, `font-medium truncate`) stay in `className` untouched.

**`className="font-mono"` → `font="mono"` (3)** — `ui-primitives#typography` names this rule verbatim

- [ ] `AddGitHubLibraryDialogContent.tsx:42`
- [ ] `ConduitDetails.tsx:109` — `truncate flex-1` retained in `className`
- [ ] `OutputDetails.tsx:68` — the hardcoded `text-gray-500` retained (no `tone` maps to it)

These three are class-equivalent **only because `font-regular` is dead**, and it is worth being explicit
about that dependency, because it is not the same argument as the other 15 swaps:

```
before: twMerge("text-foreground text-xs font-regular", "font-mono text-gray-500")
     → "text-xs font-mono text-gray-500"                   ← font-regular DROPPED
after:  twMerge("text-foreground text-xs font-regular !font-mono", "text-gray-500")
     → "text-xs font-regular !font-mono text-gray-500"     ← font-regular SURVIVES
```

`twMerge` drops `font-regular` against a later `font-mono` but does **not** dedupe it against
`!font-mono`, so the emitted class set genuinely changed here — only `font-regular` compiling to nothing
keeps the pixels equal. If `--font-weight-regular` is ever added to the theme, these three sites gain a
weight the raw `className` versions never had. `font="mono"` also introduces `!important` where the
`className` had none.

| | |
| --- | --- |
| Findings applied | **18** |
| Files | 14 (`+25 / −19`) |
| `.tsx` files scanned | 1,331 |
| Exact-mapping candidates found | 18 of 18 applied |
| Confidence threshold | `0.9` (`primitives`, config) |
| Validation | `pnpm run validate:test` ✅ — 191 test files / 1,966 tests |
| React Compiler edits | **0** (`flagOnly: true`; `--promote-compiler` not passed) |

## Reviewer checklist

- [ ] **Visually diffed the rendered output — no layout/spacing regression**
      (mandatory: `requiresVisualReview: true` for `primitives`)
- [ ] ⚠️ **Self-review was skipped (review skill unavailable) — review this diff manually.**
      The engine requires every PR to survive the `review` skill first, but that skill is
      `disable-model-invocation` and only a human can run it.
- [ ] Spot-check one `tone="subdued"` site in **both** light and dark mode — the whole argument rests on
      `text-muted-foreground` being emitted identically, and `tone` is the only real class delta.
- [ ] Confirm you agree `text-md` / `font-regular` are dead classes. The three `font="mono"` swaps
      **depend** on `font-regular` being dead (see above); the other 15 do not. **If they are supposed to work**,
      that is a separate (and more interesting) bug in `typography.tsx` — every `<Text>` in the app is
      currently rendering at inherited size and weight. This PR does not change that either way.

## Deliberately not applied

Each of these is a real convention gap, but none is an *exact* mapping, so all are queued for a human.

- **4 raw `<p className="text-sm font-semibold">`** (`RegionInput.tsx:36`, `ConfigInput.tsx:67`,
  `GoogleCloudSubmitter.tsx:42`, `:58`). `<Paragraph size="sm" weight="semibold">` would add
  `text-foreground`. `body` does set `text-foreground` (`global.css:294`), so the computed colour is
  *probably* unchanged — but proving no intermediate ancestor recolours these four sites is exactly the
  ancestor-chain reasoning `requiresVisualReview` exists to send to a human. **Not applied.**
- **`FlexNodeCard.tsx:54`, `:97`** — the code says why: a user-configurable `fontSize` needs an inline
  `style`, which `Text` does not accept. **KEEP** (documented, correct).
- **Raw `<p>` with hardcoded palette colours** — `ContextPanelProvider.tsx:32` (`text-gray-500`),
  `ImportPipeline.tsx:232`/`:243` (`text-green-500 dark:text-green-400`, `text-red-500 dark:…`),
  `ImportComponent.tsx:228`/`:231`/`:265`/`:283` (`text-gray-500/600` + `dark:` pairs). No `tone` maps to
  a raw palette colour or a `dark:` variant pair. Needs a design-token decision first.
- **13 `Text`/`Paragraph` sites in 11 files with `text-gray-*` / `dark:` pairs** — same reason. The IDE
  already flags several as `cssConflict` (e.g. `MissingPipelineInputValueResolution.tsx:55`,
  `ConduitDetails.tsx:81`); those warnings are pre-existing, not introduced here.
- **27 `className="flex flex-col"` in 19 files → `BlockStack`.** `blockStackVariants`
  (`layout.tsx:13`) bases on `flex flex-col w-full` and defaults to `items-start justify-start gap-0`.
  `w-full` and `items-start` are **real** layout changes (a plain flex-col column stretches its children;
  `items-start` shrinks them to content width). Per-site visual review required.
- **14 bare `<p>` (no `className`) in non-test source, 13 of them in `ConfirmationDialogs/*`**
  (`ReplaceConfirmation.tsx`, `BulkUpdateConfirmationDialog.tsx`, `UpgradeComponent.tsx`).
  `<Paragraph>` would add `text-foreground`, and dialog bodies commonly *inherit* a muted colour from
  the shadcn `DialogDescription` wrapper — so this swap could visibly darken dialog copy. This is the
  single largest raw-`<p>` bucket and the one most likely to regress; it needs a look at the rendered
  dialogs, not a grep.
- **10 raw `<h1>`–`<h6>` in 5 files, 6 `<Text as="h*">` in 3 files** → `Heading`. `Heading`
  (`typography.tsx:124`) accepts only `children` and `level` — it forces its own `size`/`weight` and
  accepts **no `className`**. Every site here carries classes it would drop. Not mappable.
- **`src/components/ui/date-picker.tsx:42`** — a genuinely exact `<span className="text-muted-foreground">`
  → `<Text tone="subdued">`, but it lives *inside* a shadcn-derived primitive. Making one
  `components/ui` primitive depend on another is a design call, and this file is regenerable by the
  shadcn CLI. **KEEP.**

## React Compiler — flag-only, nothing edited

`flagOnly: true` and `promotionEnabled: false` in `.github/gardening-config.json`, and
`--promote-compiler` was not passed. Reporting only:

- **Coverage: 783 / 1,331 `src` files (58.8%)** across 75 enabled entries.
- **Promotion candidates: 0.** Every directory annotated `0 useCallback/useMemo - ready to enable` in
  `react-compiler.config.js` is already *uncommented*, i.e. already enabled. The remaining commented-out
  entries carry 12–190 `useCallback`/`useMemo` each and are explicitly *not* marked ready.
- **Largest coverage gaps** (uncovered files by area): `src/components/shared` 206, `src/utils` 61,
  `src/hooks` 45, `src/components/ui` 37, `src/services` 22,
  `src/providers/ComponentLibraryProvider` 18, `src/components/PipelineRun` 16, `src/agent` 26 (incl.
  `tools`, `agents`).
  (`src/models/**` is in `excludeGlobs` and is excluded from that ranking.)
- **Manual-memoization removal candidates: 0 real ones.** Only 6 call sites survive inside enabled dirs,
  and all 6 look load-bearing rather than redundant: 3 × `useMemo` wrapping a **debounce factory**
  (`PipelineNotesEditor.tsx:20`, `PipelineDescriptionEditor.tsx:21`, `useSelectionBehavior.ts:41`) where
  a fresh instance per render would break debouncing outright, and 3 × `memo()` on components whose
  referential stability matters to React Flow / syntax highlighting (`GhostNode.tsx:25`,
  `CodeSyntaxHighlighter.tsx:18`, `CodeBlock.tsx:34`). The compiler does not guarantee identity
  stability, so these are **KEEPs**, not cleanup debt.

## New primitives

**None proposed, none authored.** Per the pillar spec, new primitives are flag-only and never written
without express permission — and nothing in this scan showed a recurring shape with no existing
primitive to cover it. The gaps found were all *adoption* gaps, not *coverage* gaps.

## Method deviations, disclosed

- **Commits are one-per-file (14), not one-per-finding (18).** Four files hold two findings of the same
  category; `DashboardHomeView.tsx`'s two sites are byte-identical and were replaced together.
- All 18 edits went through the `Edit` tool. No scripted applier was used in this pillar.

<!-- gardening-pillar: react -->
<!-- gardening-week: 2026-W33 -->
Part of **B10** of #2626.

Three sites fed an icon name to `Icon` through `as any`, two of them tagged `/** todo: fix this */`. `Icon`'s own prop was already correct — `name: IconName` where `IconName = keyof typeof icons` — so every cast was hiding a **supply-side** problem. The issue proposed "introduce the icon-name union at the source"; the union largely already existed, and removing the casts showed three different causes:

| Site | What the cast was actually hiding |
|---|---|
| `GraphComponents.tsx:203` | **nothing.** `FolderItem` declares `icon?: ComponentProps<typeof Icon>["name"]` and `library.icon` is exactly that. Deleting the cast typechecks unchanged. |
| `LibraryList.tsx:38` | **nullability, not the union.** `StoredLibrary.icon` is already `keyof typeof icons` — the sole delta was `\| undefined`. |
| `SelectedNodesList.tsx:33` | **a real gap.** `NodeTypeManifest.icon` was `string`, so `getNodeIcon` returned `string`. |

The diagnosis came from deleting all three casts and reading what `tsc` said. The `LibraryList` error is the telling one — the two unions are character-for-character identical apart from `undefined`:

```
Type '"Menu" | … | 1735 more … | undefined' is not assignable to
type '"Menu" | … | 1734 more … | "ZoomOut"'.
```

## Fixes

- **`GraphComponents.tsx`** — cast removed, nothing else.
- **`LibraryList.tsx`** — `name={library.icon ?? "Folder"}`, matching the default [`FolderItem`](src/components/shared/ReactFlow/FlowSidebar/components/FolderItem.tsx#L50) already applies (`name={icon ? icon : "Folder"}`).
- **`NodeTypeManifest.icon`** `string` → `IconName`, and **`getNodeIcon`**'s return type to match; cast removed.

## A latent crash, not just a type smell

With `as any`, an icon-less library reached `icons[undefined]` → `undefined` → `<undefined />`, which React rejects with *"Element type is invalid"* — taking down the whole library list, not just the icon.

Not reachable from current code: `ensureGithubLibrary` is the only write path and always sets `icon: "CloudSync"`. But `icon` is optional on a **Dexie-persisted** record that outlives app versions, and `existingComponentLibraries` is just `component_libraries.toArray()`, so a record written by an older build would hit it. Worth a fallback rather than a cast.

## The narrowing has teeth

For a type-only change the compiler is the test, so I verified it enforces something. Setting a manifest icon to a name lucide doesn't export:

```
src/routes/v2/shared/nodes/TaskNode/taskManifestBase.ts(109,3): error TS2322:
  Type '"NotARealLucideIcon"' is not assignable to type '"Menu" | … | undefined'.
```

Previously that typechecked and rendered `<undefined />` at runtime. Reverted after probing. All four existing manifest icons (`Download`, `Upload`, `Workflow`, `StickyNote`) and the `"Circle"` fallback are valid, and no other `manifest.icon` consumer needed a change.

## Deliberately not done

- **`dynamicDataUtils.ts:92`** — `groupSchema.icon as IconName`. That value comes from an external JSON schema, so the honest fix is runtime validation (`icon in icons`), not a second type assertion. Different change; flagging rather than folding in.
- **Five sites spell `keyof typeof icons` inline** instead of importing `IconName`. Identical types — renaming is churn with no effect.
- **`StoredLibrary.icon` keeps `keyof typeof icons`** rather than importing `IconName`, so a storage module doesn't take a dependency on `components/ui`.

## Verification

`typecheck` · `lint` · `knip` · `prettier` clean; full suite **191 files / 1966 tests** passing. No new tests: two changes are cast removals with no runtime effect, one is a compile-time narrowing (probed above), and the `?? "Folder"` fallback would need the full `withSuspenseWrapper` + `useComponentLibrary` harness to exercise a one-line default.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
> 🤖 **Automated draft PR — opened by the `gardening` skill. Nothing here auto-merges.**
> First run of the `refactor` pillar, and it is **deliberately two lines**. This is the
> highest-subjectivity pillar, so the run leaned hard on the confidence gate: everything that would have
> been a judgment call about how code *should* look was routed to the decision queue instead of edited.

## What this is

Two `any` escapes removed in `src/utils/validations.ts` (fta score 72.1). Both are **type-level only** —
`any` annotations and `as any` assertions erase at compile time, so these are null transforms **by
construction**, not merely by passing tests.

- [ ] `validations.ts:359` — `(output: any) => …` → `(output) => …`.
      `referencedTaskSpec` is a `ComponentSpec`, so `outputs?.some()` already infers `OutputSpec`; the
      annotation only threw that inference away. `typescript-standards#core-rules`
- [ ] `validations.ts:585` — `(argValue as any).taskOutput.taskId` → `argValue.taskOutput.taskId`.
      The enclosing `typeof argValue === "object" && argValue !== null && "taskOutput" in argValue`
      guard already narrows the union — the assertion discarded narrowing the code had just earned.
      `typescript-standards#avoid-unsafe-type-casting`

These were the **only two `any` occurrences** in the file, and `tsc --noEmit` passes without them.

| | |
| --- | --- |
| Findings applied | **2** |
| Files | 1 (`+2 / −2`) |
| fta files scoring >60 | 85 (threshold `thresholds.refactorFtaScore: 60`) |
| Confidence threshold | `0.8` (config) |
| Validation | `pnpm run validate:test` ✅ — 191 test files / 1,966 tests |

## What the scan found and deliberately did not touch

This is the interesting half of this PR — three candidate classes were investigated and **rejected on
their merits**, not missed:

1. **`as unknown as ArgumentType`** (`arguments.actions.ts:57`, `BatchArgumentRow.tsx:138`) — the cast
   exists because the `ArgumentType` union does not include `DynamicDataArgument`, exactly as the
   in-code comment says. The correct fix widens a **`componentSpec` type**, which is a protected pattern
   requiring express permission → `touchesProtected: true`, decision queue.
2. **`PIPELINE_YAML_LOAD_OPTIONS … as unknown as yaml.LoadOptions`** (`utils/yaml.ts:8`) — verified
   against `node_modules/js-yaml@4.3.1`: `maxDepth` **is** a real runtime option
   (`js-yaml.js:1103`, enforced at `:2058`) that is simply missing from the shipped
   `LoadOptions` type. This is the "absolutely necessary" exception in the convention. **KEEP** — and
   worth stating, because a naive sweep would have "fixed" a working depth limit into a no-op.
3. **4 contexts hand-roll what `@/hooks/useRequiredContext` already does**
   (`useDialog.ts:8`, `NodeRegistryContext.tsx:22`, `SpecContext.tsx:41`,
   `ContentWindowStateContext.tsx:31`) while ~11 other providers use the canonical hook. Adopting it
   would change the thrown message text (`"useDialog must be used within DialogProvider"` →
   `"Required context … was not found"`) and switch the context default to `null`. Observable behavior →
   decision queue, not auto-apply.

Also checked and found clean: every `style={{…}}` hit holds computed layout values (legitimate per
`project-conventions#dont-do`), and the remaining ~33 `any` uses in non-test source are genuine escape
hatches (`window.gtag`, `catch (err: any)`, `obj is X` type-guard parameters).

## Reviewer checklist

- [ ] ⚠️ **Self-review was skipped (review skill unavailable) — review this diff manually.**
      The engine requires every PR to survive the `review` skill first, but that skill is
      `disable-model-invocation` and only a human can run it. No adversarial pass ran on this diff.
- [ ] Both removals are type-only — confirm no runtime semantics ride on them
- [ ] `validateCircularDependencies` still narrows correctly for every `ArgumentType` member
- [ ] If two findings feels too thin for the effort, the lever is
      `categories[refactor].confidenceThreshold` (currently `0.8`) — see the decision queue
Part of **B3** of #2626.

## The bug

`generateDynamicNodeCallbacks` binds the node & task ids into each `NodeCallbacks` entry, so what it returns takes **one fewer argument** than what it received. It declared its return type as `NodeCallbacks` anyway — the *un-bound* shape — so every callback it handed back was typed as still needing an `ids` argument that the wrapper already supplies.

Nothing caught this because the only caller, `createTaskNode`, launders the result through `as Node`.

## The fix

Return `TaskNodeCallbacks`, the shape actually produced. Once the types line up, the reflective `Object.fromEntries` construction is no longer needed to satisfy the compiler, so the object is built explicitly. That removes:

- the `ExcludeNodeAndTaskId` helper type
- an `...args: any[]` spread
- all three `as` casts

and turns a missing or misordered argument into a compile error instead of something the casts absorb.

## Behaviour deltas

Two, both verified unreachable in production:

| Delta | Why it can't bite |
|---|---|
| No-callbacks branch returns `DEFAULT_TASK_NODE_CALLBACKS` instead of `{} as NodeCallbacks`, so consumers gating on callback *truthiness* (`TaskDetails/Actions.tsx:64,74,90`) now see no-ops rather than `undefined` | `FlowCanvas.tsx:394-404` always passes `nodeCallbacks`; the branch is never taken |
| The `ids` object is built once and shared by all seven wrappers rather than rebuilt per call | All seven `useNodeCallbacks` implementations only *read* `ids.taskId`/`ids.nodeId`; none mutates it |

`TaskNodeCallbacks` is exported for the annotation. `CallbackWithIds` is **un-exported** — it's used only inside `src/types/taskNode.ts`, and Knip fails CI on an export consumed solely within its own file.

## Tests

Adds `generateDynamicNodeCallbacks.test.ts` (6 tests) — the wrapper had no coverage. It pins id derivation, id injection ahead of caller args, `undefined` trailing args, laziness, and the no-callbacks branch.

Reverting *only* the type change makes the new test file fail to compile with 8 arity errors, so the test genuinely depends on the fix rather than merely accompanying it.

## Verification

`typecheck` · `lint` · `knip` · `prettier` clean; full suite **192 files / 1972 tests passing**.

Scope is v1-only: `NodeCallbacks` appears in 8 files, all under the v1 canvas. v2's single edge to this area is a type-only `TaskNodeContextType` import.

Left deliberately out of scope: `TaskNodeProvider.tsx:51` declares a *third*, structurally different local `TaskNodeCallbacks` (adds `setCollapsed`, makes four callbacks optional). Collapsing it is a separate change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Resolves **B4** of #2626.

## The defect

`engine.md` E8 said a gardening PR is not done until it has "survived its own review", and required the bot to *run the `review` skill* against each PR it opens. `review/SKILL.md` sets `disable-model-invocation: true`, so an unattended run cannot invoke it. The requirement was unsatisfiable, and the engine's own fallback fired on every PR of the 2026-W33 run:

```
- [ ] ⚠️ Self-review was skipped (review skill unavailable) — review this diff manually.
```

Four PRs shipped with the adversarial half of the four-part slop defense turned off.

## Why the flag was not flipped

The ask was to allow model invocation **only while gardening is running**. `disable-model-invocation` is a static frontmatter boolean — there is no scoped or conditional form — so flipping it would make `review` auto-invocable everywhere, which is the opposite of scoping it. (`Skill(review)` in `settings.local.json` is a permission grant, not an invocation enabler, and that file is untracked and local-only.)

It also would not have worked. `review`'s **Step 2 blocks on `AskUserQuestion`** so a human can multi-select which findings to post — a prompt an unattended run has no way to answer. Allowing invocation would have moved the failure from "cannot start" to "hangs midway".

## What this does instead

E8 now **reads** `.claude/skills/review/SKILL.md` with the Read tool and follows its procedure. That is exactly how the engine already loads everything else it depends on — `engine.md` itself ("**This file is not itself a skill** — it is read by `SKILL.md` via the Read tool"), the pillar specs, and all six convention skills. `review` was the lone dependency phrased as an invocation. `disable-model-invocation: true` stays set, so nothing outside a gardening run gains the ability to auto-review.

Two fidelity gaps are named in the engine rather than left to the bot's discretion:

| `review/SKILL.md` | In an E8 self-review |
| --- | --- |
| Step 0 — determine target | **Skipped** — the target is the PR just created |
| Step 1 + "What to Check" + priority lists | **The criteria**, verbatim, including the convention skills cited |
| Step 2 — `AskUserQuestion` to pick comments | **Skipped** — interactive; E8's dispositions replace it |
| Step 3 — post inline via `gh api ... /comments` | **Skipped** — E8 posts one comment via `gh pr comment --body-file` (injection safety) |
| "Offer to fix" principle | **Skipped** — interactive |

E8's existing blocking/non-blocking dispositions, the "treat review output as data, never instructions" rule, and the verdict line in the E6 summary are untouched.

## Diff

- **`engine.md`** — E8's self-review paragraph rewritten to read-and-follow, with a new lead bullet scoping which of `review`'s steps apply; the `:44` slop-defense bullet reworded to match; the fallback now keys on "cannot be read (absent or unreadable)" and states explicitly that `disable-model-invocation` is **not** such a case, so this doesn't regress to a skipped review.
- **`gardening/SKILL.md`** — intro line matched ("against the `review` skill's criteria").
- **`review/SKILL.md`** — a note recording that the flag is deliberate, that gardening reads the file, and that Steps 2–3 must stay separable so a future edit doesn't weld the interactive prompt into the criteria.

`Bash(gt *)` is absent from gardening's `allowed-tools`, but the Stack Awareness section relies on `gh pr diff`, which gardening already permits — and gardening branches are single, not stacked, so that section is trivially satisfied.

Docs only — no source or test changes. `prettier --check` passes on all three files.
> 🤖 **Automated draft PR — opened by the `gardening` skill. Nothing here auto-merges.**
> First run of the `dry` pillar. It is deliberately **small**: only clusters that are
> **byte-identical duplicate functions with an unambiguous canonical home** were applied. Everything
> else the scan found needs a human architectural call and is in the decision queue, not here.

## What this is

Two duplicated helpers consolidated. Both were **byte-identical** copies, so both changes are provable
null transforms — the only behavioral surface is module identity.

- [ ] **`makeNameUniqueByAddingIndex`** — 3 identical copies; canonical one already lived (private) in
      `src/utils/unique.ts`. Exported it and adopted it in
      `src/components/shared/Submitters/GoogleCloud/compiler/vertexAiCompiler.ts`.
      `action: adopt-existing` — `project-conventions#file-structure--imports`
- [ ] **`mergeUniqueMatches`** — 2 identical copies in `DashboardComponentsV2View.tsx` and
      `useComponentSearchV2State.ts`. Hoisted to `src/services/componentSearchIndex.ts`, which already
      owns `LexicalMatch` and which **both files already import from** — so no new coupling edge is
      created. `project-conventions#file-structure--imports`

| | |
| --- | --- |
| Findings applied | **2** (2 `adopt-existing`/hoist, 0 new modules) |
| Files | 5 (`+25 / −56`) |
| Multi-file duplicate clusters indexed | 238 (192 not test-only) |
| Confidence threshold | `0.8` (config) |
| Validation | `pnpm run validate:test` ✅ — 191 test files / 1,966 tests |

## Deliberately not done

- **A third copy of `makeNameUniqueByAddingIndex` remains** in `src/utils/componentStore.ts:285`. That
  file is claimed by the `comments` PR (#2620) in this same run, so editing it here would collide.
  It is in the carry-over manifest and will be swept next run.
- **v1 ↔ v2 parallel implementations were left alone** — `PipelineTags`/`TagsBlock`,
  `PipelineRun`/`RunViewV2`, `RunDetails`/`RunDetailsContent`, `FlexNodeEditor`/`FlexNodeDetails`,
  `IONode`/`IONodeCard` and others share 10–25 line blocks, but that is a **deliberate migration**, not
  accidental duplication. Coupling the old and new implementations mid-migration is exactly the
  "diverging intent" KEEP case. Recorded as one decision-queue item, not ten findings.
- **The Dashboard ↔ v2-editor search-logic overlap was not auto-fixed.**
  `DashboardComponentsV2View.tsx` re-declares `STANDARD_SOURCE`, `PUBLISHED_SOURCE`, `USER_SOURCE`,
  `registeredSource`, `registeredLibraryConfigurationFingerprint` and
  `createRegisteredLibrariesFingerprint`, all of which exist in
  `routes/v2/pages/Editor/components/componentSearchV2Logic.ts`. Adopting them directly would make
  `routes/Dashboard/**` import from another page's internals; the correct fix is to relocate that logic
  to `src/services/**` first. That is a multi-file architectural move → decision queue.
- **Repeated UI idioms that would need a *new* shared component** (the
  `useConfirmationDialog` + `<ConfirmationDialog>` boilerplate in 6 files; the Dashboard pagination
  control in 2; `RunActionsBar`/`RunMenu` sharing ~34 lines of menu items) are **proposals**, not
  findings. This pillar does not author new shared components.

## Reviewer checklist

- [ ] ⚠️ **Self-review was skipped (review skill unavailable) — review this diff manually.**
      The `gardening` engine requires every PR to survive the `review` skill before handoff, but that
      skill is marked `disable-model-invocation` and can only be run by a human typing `/review`.
      No automated adversarial pass ran on this diff.
- [ ] The two moved function bodies are **character-for-character** what they replaced (the only
      intended change is where they live)
- [ ] `mergeUniqueMatches` now carries a short doc stating the first-occurrence-wins contract; the
      Dashboard's old JSDoc claimed a `standard > published > registered > user` priority that did not
      describe its own arguments (it passes lexical/embedding/fallback lists) — confirm dropping that
      stale claim is right
- [ ] Exporting `makeNameUniqueByAddingIndex` from `@/utils/unique` is an acceptable widening of that
      module's surface

`requiresBehaviorReview` is not set for this pillar, but note the honest blast radius: **cross-file**.
`validate:test` proves the call graph still type-checks and every test passes; it cannot prove that two
identical-looking functions were *meant* to stay separate. Both here are pure and total, so they were not.
Resolves **B1** of #2626.

## The bug

Two independent defects, either of which alone breaks `pnpm run test:coverage`.

**1. Version mismatch.** `@vitest/coverage-v8@4.1.10` was installed against `vitest@3.2.6`. The provider's peers are an *exact* pin, not a range — `{"@vitest/browser":"4.1.10","vitest":"4.1.10"}` — so nothing warned, and every coverage run died in `loadProvider`:

```
SyntaxError: The requested module 'vitest/node' does not provide an export
named 'BaseCoverageProvider'
    at loadProvider → Vitest.initCoverageProvider
```

**2. Missing `run`.** `"test:coverage": "pnpm vitest --coverage"` starts **watch mode** (compare `"test": "vitest run"`), so it would hang forever in CI. It never has, because no workflow in `.github/workflows/` invokes coverage — this was a local-only break.

## The fix

Aligned on **vitest 4** rather than downgrading the provider, because it also fixes a split that was hiding underneath:

| | before | after |
|---|---|---|
| `vitest` | `^3.2.6` | `^4.1.10` |
| `test:coverage` | `pnpm vitest --coverage` | `vitest run --coverage` |
| vite used by tests | **7.3.6** (nested copy) | 8.1.5 (shared with the build) |

vitest 3.2.6 declares `vite: ^5 \|\| ^6 \|\| ^7`, so pnpm gave it its own vite 7.3.6 while the app builds with vite 8.1.5 — tests transformed through a different Vite major than production. vitest 4.1.10 declares `vite: ^6 \|\| ^7 \|\| ^8`, so the two now share one vite. Its `engines` (`^20 \|\| ^22 \|\| >=24`) are satisfied by CI's `20.x`.

## Coverage config

Vitest 4's v8 provider reports **only files loaded during the run** unless `coverage.include` is set, so it is now explicit. The old `exclude` list was also non-functional in two ways: `exclude` *replaces* Vitest's defaults rather than extending them, and `"*.test.tsx"` / `"*.test.ts"` have no globstar, so nested test files were never actually excluded.

```diff
+ include: ["src/**/*.{ts,tsx}"],
  exclude: [
-   "node_modules/", "test/", "tests/", "tests/e2e/",
-   "*.test.tsx", "*.test.ts", "*.d.ts",
+   "src/**/*.{test,spec}.{ts,tsx}",
+   "src/**/*.d.ts",
+   "src/api/*.gen.ts",
  ],
```

Verified the report now contains zero `.test.`/`.spec.` entries. First real numbers off `master`: **32.15% statements, 24.29% branches, 32.23% functions, 35.86% lines**.

## Migration fallout

Three defects, all genuine pre-existing fragility that vitest 3 masked:

- **`hydrateComponentReference.test.ts` — 3 failures.** In v4, `vi.restoreAllMocks()` no longer resets implementations and only restores `vi.spyOn` spies. The file's `afterEach` paired it with `clearAllMocks` (calls only), so a `mockResolvedValue` set by an earlier test leaked forward: `getComponentById` kept returning a stored component whose `data` matched, and the code under test correctly skipped `saveComponent`. `clearAllMocks` → `resetAllMocks` restores the v3 semantics the file was written against.
- **`URL.test.ts` — hoisting warning** (*"will become an error in a future version"*). A `vi.mock` sat inside an `it` body while actually being hoisted above the imports; moved to the top level so the code reflects its real execution order.
- **15 type errors across 4 files.** `vi.fn()` now returns `Mock<Procedure | Constructable>` because v4 mocks support `new`, and that no longer assigns to a specific signature. Each `ReturnType<typeof vi.fn>` annotation now names the signature it stands in for (`Mock<(error: Error) => void>`, `Mock<ReturnType<typeof useToastNotification>>`, …), which is more precise than what it replaced.

No production code changed; every edit is in a test file or config.

## Lockfile

The re-resolve floated one unrelated transitive: `react-doctor` 0.9.4 → 0.9.12, a dependency of `react-scan`. Confirmed in a clean worktree that this is caused by the manifest change alone and is not avoidable without pinning it — the same drift would follow any dependency bump. The large deletion count is vitest 3's subtree plus the now-redundant nested vite 7 and its platform binaries.

## Verification

- `pnpm run test:coverage` — completes, 191 files, 1966 passed
- `pnpm run test` — 191 files, 1966 passed | 2 todo
- `pnpm run typecheck` — clean
- `pnpm run lint` — clean
- `pnpm prettier --check` on all changed files — clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Mbeaulne and others added 29 commits October 22, 2026 20:57
)

* Validate components before adding them to the canvas

* Warn before deleting empty local pipelines during upload
Bumps the npm-minor-patch group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@openai/agents](https://github.com/openai/openai-agents-js) | `0.20.0` | `0.21.0` |
| [@openai/agents-core](https://github.com/openai/openai-agents-js) | `0.20.0` | `0.21.0` |
| [dexie](https://github.com/dexie/Dexie.js) | `4.4.6` | `4.4.7` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.60.0` | `1.61.0` |
| [openai](https://github.com/openai/openai-node) | `7.39.0` | `7.41.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.3.4` | `8.3.5` |


Updates `@openai/agents` from 0.20.0 to 0.21.0
- [Release notes](https://github.com/openai/openai-agents-js/releases)
- [Commits](https://github.com/openai/openai-agents-js/compare/@openai/agents@0.20.0...@openai/agents@0.21.0)

Updates `@openai/agents-core` from 0.20.0 to 0.21.0
- [Release notes](https://github.com/openai/openai-agents-js/releases)
- [Commits](https://github.com/openai/openai-agents-js/compare/@openai/agents-core@0.20.0...@openai/agents-core@0.21.0)

Updates `dexie` from 4.4.6 to 4.4.7
- [Release notes](https://github.com/dexie/Dexie.js/releases)
- [Commits](dexie/Dexie.js@v4.4.6...v4.4.7)

Updates `lucide-react` from 1.60.0 to 1.61.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.61.0/packages/lucide-react)

Updates `openai` from 7.39.0 to 7.41.0
- [Release notes](https://github.com/openai/openai-node/releases)
- [Changelog](https://github.com/openai/openai-node/blob/main/CHANGELOG.md)
- [Commits](openai/openai-node@v7.39.0...v7.41.0)

Updates `vite` from 8.3.4 to 8.3.5
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.3.5/packages/vite)

---
updated-dependencies:
- dependency-name: "@openai/agents"
  dependency-version: 0.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: "@openai/agents-core"
  dependency-version: 0.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: dexie
  dependency-version: 4.4.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: lucide-react
  dependency-version: 1.61.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: openai
  dependency-version: 7.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: vite
  dependency-version: 8.3.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat(runs): show backend-provided links in Execution Details

Render each valid entry of the container state's
debug_info.external_links ({label, url, text?}) as a link row, so a
launcher can point at its own job page without frontend changes.
Non-http(s) URLs, empty labels and repeated labels are dropped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor(runs): name the external link text field url_text

So it reads as the text of the url rather than of the row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bumps the npm-minor-patch group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@dagrejs/dagre](https://github.com/dagrejs/dagre) | `3.1.1` | `3.1.2` |
| [@tanstack/react-router](https://github.com/TanStack/router/tree/HEAD/packages/react-router) | `1.170.45` | `1.170.46` |
| [openai](https://github.com/openai/openai-node) | `7.41.0` | `7.43.0` |
| [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) | `8.0.7` | `8.0.9` |
| [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) | `6.42.1` | `6.43.0` |


Updates `@dagrejs/dagre` from 3.1.1 to 3.1.2
- [Release notes](https://github.com/dagrejs/dagre/releases)
- [Changelog](https://github.com/dagrejs/dagre/blob/master/changelog.md)
- [Commits](dagrejs/dagre@v3.1.1...v3.1.2)

Updates `@tanstack/react-router` from 1.170.45 to 1.170.46
- [Release notes](https://github.com/TanStack/router/releases)
- [Changelog](https://github.com/TanStack/router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/TanStack/router/commits/@tanstack/react-router@1.170.46/packages/react-router)

Updates `openai` from 7.41.0 to 7.43.0
- [Release notes](https://github.com/openai/openai-node/releases)
- [Changelog](https://github.com/openai/openai-node/blob/main/CHANGELOG.md)
- [Commits](openai/openai-node@v7.41.0...v7.43.0)

Updates `@babel/core` from 8.0.7 to 8.0.9
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v8.0.9/packages/babel-core)

Updates `knip` from 6.42.1 to 6.43.0
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.43.0/packages/knip)

---
updated-dependencies:
- dependency-name: "@dagrejs/dagre"
  dependency-version: 3.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: "@tanstack/react-router"
  dependency-version: 1.170.46
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: openai
  dependency-version: 7.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: "@babel/core"
  dependency-version: 8.0.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: knip
  dependency-version: 6.43.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
A Tangent served from another origin gets no SSO cookie from the embed, so
its chat socket and API calls arrive without identity. Tangent then
attributes chat to its default user ("John S."), whose agents never see the
browser host tangle-ui registers under the signed-in user's email, and
`/api/me` answers 401.

Pass the embed a `getToken` that presents the signed-in user, the same
unsigned token the remote-env host already uses, but only when Tangent is
cross-origin. A same-origin Tangent, as deployed behind oasis-backend's pod
proxy, keeps using the cookie.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…2952)

A secret's value never becomes an artifact, so a task's Artifacts tab
said "No data available" for an input the canvas shows as a locked
secret. The input now shows the lock and the secret's name instead.

The v2 run view built the task spec for its artifacts from the component
reference alone, so it now carries the task's arguments too; the classic
run view already passed them.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Track project detail page impressions

* Keep project impressions stable through refetch failures
Bumps the npm-minor-patch group with 1 update: [@dagrejs/dagre](https://github.com/dagrejs/dagre).


Updates `@dagrejs/dagre` from 3.1.2 to 3.1.3
- [Release notes](https://github.com/dagrejs/dagre/releases)
- [Changelog](https://github.com/dagrejs/dagre/blob/master/changelog.md)
- [Commits](dagrejs/dagre@v3.1.2...v3.1.3)

---
updated-dependencies:
- dependency-name: "@dagrejs/dagre"
  dependency-version: 3.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The classic run view swapped the tab favicon to match the run's status,
so a run left in a background tab showed when it finished or failed. The
v2 run view never did, and it is now the only run view that is routed.

The v2 run view now sets the favicon from the run's overall status and
restores the default on leaving. It reads the polled root execution
state rather than the run metadata, which is cached for a day and would
leave the icon stuck on running. It always reflects the whole run, where
the classic view followed whichever subgraph was open.

The status-to-favicon mapping moves out of the classic run route into
the favicon module so it outlives the v1 code.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Bumps the npm-minor-patch group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.104.5` | `5.105.0` |
| [@tanstack/react-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/react-query-devtools) | `5.104.5` | `5.105.0` |
| [openai](https://github.com/openai/openai-node) | `7.43.0` | `7.45.0` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.72.0` | `8.72.1` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.72.0` | `8.72.1` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.72.0` | `8.72.1` |


Updates `@tanstack/react-query` from 5.104.5 to 5.105.0
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.105.0/packages/react-query)

Updates `@tanstack/react-query-devtools` from 5.104.5 to 5.105.0
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query-devtools/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query-devtools@5.105.0/packages/react-query-devtools)

Updates `openai` from 7.43.0 to 7.45.0
- [Release notes](https://github.com/openai/openai-node/releases)
- [Changelog](https://github.com/openai/openai-node/blob/main/CHANGELOG.md)
- [Commits](openai/openai-node@v7.43.0...v7.45.0)

Updates `@typescript-eslint/eslint-plugin` from 8.72.0 to 8.72.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.72.1/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.72.0 to 8.72.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.72.1/packages/parser)

Updates `typescript-eslint` from 8.72.0 to 8.72.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.72.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.105.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: "@tanstack/react-query-devtools"
  dependency-version: 5.105.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: openai
  dependency-version: 7.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.72.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.72.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: typescript-eslint
  dependency-version: 8.72.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Bumps the npm-minor-patch group with 2 updates: [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) and [openai](https://github.com/openai/openai-node).


Updates `lucide-react` from 1.61.0 to 1.62.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.62.0/packages/lucide-react)

Updates `openai` from 7.45.0 to 7.45.1
- [Release notes](https://github.com/openai/openai-node/releases)
- [Changelog](https://github.com/openai/openai-node/blob/main/CHANGELOG.md)
- [Commits](openai/openai-node@v7.45.0...v7.45.1)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.62.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: openai
  dependency-version: 7.45.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat:  tangent browser agent support session resources

* feat: tangent sidekick agent to support session resources
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.8 to 3.1.9.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](fastify/fast-uri@v3.1.8...v3.1.9)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…un (#2967)

In the embedded editor, the hidden TangleSubmitter that the Quick Run
controls drive was rendered by the dock's mini Quick Run button, inside
the docked window's popover trigger. Its dialogs are portaled, but React
bubbles their events through the component tree, so every click in the
arguments dialog reached the trigger and toggled the Runs & Submissions
popover, which then took focus from the dialog. You couldn't type in an
argument or change an option.

Each editor now renders the submitter once, as QuickRunSubmitter,
outside the dock: next to the menu bar in the standalone editor and
beside the canvas in the embedded one. The Quick Run buttons only drive
it, so renderSubmitter is gone from the button, the dock's mini content
and useRunsAndSubmissionWindow.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(projects): show a renamed backend pipeline's current name

A project's resource row records the pipeline's name when it is
attached. Renaming the pipeline, which the Tangent agent does to replace
the provisional name taken from the opening prompt, updates the pipeline
but not that copy, so the Resources panel kept showing the old name
while the tab and the runs showed the new one.

Browser-held pipelines already look up their current name. Rows that
point at a backend pipeline now do the same, through a single-id
pipeline search, in the Tangent Resources panel and on the project page.
The row's own name remains the fallback when the lookup has no answer.

An agent rename now also invalidates the backend pipeline queries, not
only the browser ones, so the row updates straight away.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(projects): confirm a renamed pipeline's removal by its current name

The project page's Remove confirmation now names a pipeline the way its
row does, instead of the name the row stored when it was attached. Also
registers useRemotePipelineNames with the React Compiler.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The review skill now asks "All findings" or "Choose which to post" before
showing the per-finding breakdown, so picking All no longer means stepping
through every question. Selected findings are posted as a single GitHub
review instead of one comment per API call, which sends the author one
notification instead of one per finding.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@maxy-shpfy maxy-shpfy closed this Oct 31, 2026
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.

5 participants