Skip to content

fix(dashboard): orchestration canvas fase 3 — canvas polish (#12392) - #12983

Merged
diegosouzapw merged 6 commits into
release/v3.8.51from
feat/orch-fase3-b
Sep 10, 2026
Merged

diegosouzapw merged 6 commits into
release/v3.8.51from
feat/orch-fase3-b

Conversation

@diegosouzapw

@diegosouzapw diegosouzapw commented Sep 7, 2026 •

Copy link
Copy Markdown
Owner

Fase 3 do Orchestration Canvas, PR-B: os nove itens de dívida da issue #12392, todos com causa já diagnosticada nas reviews da Fase 2. Nenhuma feature nova.

Spec: _tasks/superpowers/specs/2026-09-03-orchestration-canvas-fase3-design.md · Plano: _tasks/superpowers/plans/2026-09-03-orchestration-canvas-fase3.md.

Os dois itens de fundo

staleSince avançava a cada poll (hooks/useOrchestrationSnapshot.ts). buildSourceStatuses carimbava nowIso em toda fonte que falhasse, em todo tick. Duas consequências: a linha de stale dizia "parado desde o último poll" por mais tempo que a fonte estivesse fora, e — como snapshotContentKey serializa a lista de fontes — a identidade do snapshot mudava a cada 5s enquanto qualquer fonte estivesse quebrada, refazendo o render. Agora as ramificações de falha reusam o staleSince que aquela fonte já tinha no status anterior, via o updater funcional de setStatuses (sem ref lido em render, sem setState em corpo de efeito).

Fonte que TINHA dados e passa a falhar não sinalizava (model/mergeSnapshot.ts). buildRootAndSourceEdges só marcava sourceIssue na fonte sem nó nenhum, materializando um placeholder. Uma fonte que caía no meio da sessão mantinha o SourceNode com cara de saudável para sempre. Agora toda fonte !ok/offline é sinalizada: sem nó, cria o placeholder como antes; com nó, o nó é substituído por uma cópia { ...node, sourceIssue, staleSince } — nunca mutado, porque o objeto original segue referenciado por parts e a função é pura.

Os sete pontuais

  1. Clique em chip cancela o debounce pendente da busca — armado, ele disparava ~300ms depois com um setParams fechado sobre a query anterior e revertia o chip em silêncio.
  2. aria-label no input de busca: o placeholder sozinho não é nome acessível.
  3. parseCsvSet faz trim por token, então ?state=running, failed parseia como a forma sem espaço em vez de descartar o valor.
  4. toggleCsv estava duplicado na toolbar e no page client; passou para model/urlParams.ts, definido uma vez.
  5. AgentsTab distingue "nada rodando" de "o filtro não casou nada": com filtro ativo e zero work nodes mostra noMatches + botão de limpar filtros, em vez dos CTAs de configuração, que ali seriam conselho errado.
  6. Teto de partículas: acima de 40 arestas ativas visíveis (pós-collapse), orchestrationToFlow desliga particles e o StatusEdge renderiza só o traço colorido, sem as 3 partículas SMIL por aresta.
  7. O banner de erro do drawer some quando a ação repetida dá certo.

Divergências entre plano e código, resolvidas pelo código

  • O plano previa toggleCsv(raw: string | null, value: string). As duas cópias reais usam (current: ReadonlySet<T>, value: T) e nenhum caller tem o CSV cru em mãos. Ficou na forma baseada em Set; comportamento observável idêntico (lista vazia ⇒ null, param sai da URL).
  • orchestrationToFlow não tinha contagem de arestas ativas — foi criada aqui, sobre as arestas visíveis, que é o que o operador enxerga.
  • No SourceNode existente escrevi só sourceIssue/staleSince, não sublabel: o haystack de busca de filterSnapshot.ts é label + sublabel + id, e escrever "error" ali faria a fonte casar com a query "error".

Testes

TDD em todas as tasks — teste escrito, visto falhar, depois passar. useOrchestrationSnapshot.test.tsx (staleSince fixado em dois polls seguidos, re-carimbado após recuperação, identidade estável do snapshot), orchestrationModel.test.ts (nó existente que passa a falhar, com prova de pureza do objeto de entrada), orchestrationUrlParams.test.ts (novo), orchestrationTabs/Drawer/Nodes/ToFlow/Page. Duas asserções deepEqual pré-existentes foram estendidas com particles, nenhuma removida ou enfraquecida.

i18n: noMatches traduzida de verdade nos 41 locales, cada uma escrita contra as chaves vizinhas do próprio arquivo (emptyTitle, stateRunning, searchPlaceholder). Sem i18n:sync-ui, sem __MISSING__.

Gates

typecheck:core limpo · check:dashboard-typecheck OK (206 erros pré-existentes, dentro do baseline) · check:cycles OK · test:vitest 465/465 · lint exit 0 nos 17 arquivos tocados · i18n glossary/ratio/ui-coverage/value-drift PASS.

⚠️ base-red inherited: #12732

Quatro vermelhos vêm do tip de release/v3.8.51, nenhum desta PR:

Vermelho Causa na base Fix em voo
check:docs-all contagem de migrations 169→171 e "19 routing strategies"→20 #12959
i18n:check 30 fontes de docs/ mudaram desde a última tradução —
tests/unit/zai-web-silent-empty-repro.test.ts código de erro virou bad_gateway sem atualizar o teste —
aviso de supressões órfãs no lint entradas mortas em open-sse/services/combo.ts, tlsClientBase.ts, useProviderConnections.ts, catalogCache.ts —

Nenhum desses arquivos é tocado por esta PR.

Closes #12392


Atualização sobre os vermelhos herdados (reproduzidos no tip limpo): as quatro shards de Unit Tests fast-path também estão vermelhas na base. Prova de isolamento: a #12983 toca só UI do dashboard, i18n e changelog, e apresenta exatamente o mesmo conjunto de falhas da #12988, que toca rotas A2A/Conductor. Rodando no checkout principal em d6f31501, sem nenhuma alteração aplicada:

node --import tsx/esm --test tests/unit/chatcore-translation-paths.test.ts
✖ chatCore locks per-model quota failures without dropping quota helper references
  AssertionError: false !== true   (tests/unit/chatcore-translation-paths.test.ts:3249)

Inventário por shard e o resto da evidência estão em #12732 (comment). Nenhum dos arquivos envolvidos é tocado por esta PR.

buildSourceStatuses stamped nowIso on every failing source at every poll, so
the stale indicator reported "since the last poll" instead of the first
failure — and, because snapshotContentKey serializes sources, the snapshot
identity churned on every tick while any source was down.

The failing branches now reuse the staleSince already held by that source in
the previous status list, via the functional setStatuses updater (no ref read
during render, no setState inside an effect body).

Refs #12392
buildRootAndSourceEdges only materialized a placeholder SourceNode when the
failing source had no node at all. A source that already had work nodes and
then started failing (or went offline) kept its healthy-looking SourceNode
forever: no ⚠, no stale styling, no `sourceStale` line — the operator saw a
normal source while it was actually broken.

Now every non-ok/offline source is flagged: when its SourceNode is missing the
placeholder is created as before; when it exists, the node is replaced by a
copy carrying `sourceIssue` and `staleSince`. The copy (never a mutation) keeps
the function pure — the original object is still referenced by the caller's
`parts`, the same trap the droppedByState aliasing fix covered.

Tests: three cases in tests/unit/ui/orchestrationModel.test.ts — existing node
starting to fail (flags set, work nodes kept, no duplicate node, input object
untouched), existing node going offline (no invented staleSince), and a healthy
source staying free of both fields.

Refs #12392
Seven pointwise fixes on the Orchestration Canvas, each covered by a test:

1. Debounce x chip race: every chip/clear write in OrchestrationToolbar now cancels
   the pending search timer first. Left armed, it fired ~300ms later with a setParams
   closed over the pre-chip query string and silently reverted the chip.
2. The search input carries an aria-label (searchPlaceholder) — the placeholder alone
   is not an accessible name.
3. parseCsvSet trims each token, so `?state=running, failed` parses like the unpadded
   form instead of dropping the padded value.
4. toggleCsv was duplicated in the toolbar and the page client; both now import the
   single definition from the new model/urlParams.ts (pure, never mutates its inputs).
5. AgentsTab tells "nothing running" apart from "the filter matched nothing": with an
   active filter and no work node it renders noMatches + a clear-filters button instead
   of the setup CTAs, which would be wrong advice there.
6. Particle cap: orchestrationToFlow stamps `particles` on every edge and turns it off
   above PARTICLE_EDGE_CAP (40) simultaneously active edges — StatusEdge then renders
   the colored stroke without its 3 SMIL particles per edge.
7. The drawer's error banner clears when an action succeeds, so a recovered failure
   does not stay on screen.

Only `noMatches` is added to en.json here; the other locales are task B4.

Refs #12392
Real translations for orchestration.noMatches in the 41 non-English locales,
each one written against that file's own neighbouring keys (emptyTitle,
stateRunning, searchPlaceholder) so the wording for "task" and "filter"
matches what the locale already uses. No i18n:sync-ui, no __MISSING__ left.

Adds the changelog fragment for the nine PR-B fixes.

Closes #12392
@diegosouzapw
diegosouzapw merged commit a1b2601 into release/v3.8.51 Sep 10, 2026
8 of 11 checks passed
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…zapw#12392) (diegosouzapw#12983)

* fix(dashboard): keep the first failure timestamp in sourceStale

buildSourceStatuses stamped nowIso on every failing source at every poll, so
the stale indicator reported "since the last poll" instead of the first
failure — and, because snapshotContentKey serializes sources, the snapshot
identity churned on every tick while any source was down.

The failing branches now reuse the staleSince already held by that source in
the previous status list, via the functional setStatuses updater (no ref read
during render, no setState inside an effect body).

Refs diegosouzapw#12392

* fix(dashboard): flag a source that starts failing after it had data

buildRootAndSourceEdges only materialized a placeholder SourceNode when the
failing source had no node at all. A source that already had work nodes and
then started failing (or went offline) kept its healthy-looking SourceNode
forever: no ⚠, no stale styling, no `sourceStale` line — the operator saw a
normal source while it was actually broken.

Now every non-ok/offline source is flagged: when its SourceNode is missing the
placeholder is created as before; when it exists, the node is replaced by a
copy carrying `sourceIssue` and `staleSince`. The copy (never a mutation) keeps
the function pure — the original object is still referenced by the caller's
`parts`, the same trap the droppedByState aliasing fix covered.

Tests: three cases in tests/unit/ui/orchestrationModel.test.ts — existing node
starting to fail (flags set, work nodes kept, no duplicate node, input object
untouched), existing node going offline (no invented staleSince), and a healthy
source staying free of both fields.

Refs diegosouzapw#12392

* fix(dashboard): canvas polish batch (diegosouzapw#12392)

Seven pointwise fixes on the Orchestration Canvas, each covered by a test:

1. Debounce x chip race: every chip/clear write in OrchestrationToolbar now cancels
   the pending search timer first. Left armed, it fired ~300ms later with a setParams
   closed over the pre-chip query string and silently reverted the chip.
2. The search input carries an aria-label (searchPlaceholder) — the placeholder alone
   is not an accessible name.
3. parseCsvSet trims each token, so `?state=running, failed` parses like the unpadded
   form instead of dropping the padded value.
4. toggleCsv was duplicated in the toolbar and the page client; both now import the
   single definition from the new model/urlParams.ts (pure, never mutates its inputs).
5. AgentsTab tells "nothing running" apart from "the filter matched nothing": with an
   active filter and no work node it renders noMatches + a clear-filters button instead
   of the setup CTAs, which would be wrong advice there.
6. Particle cap: orchestrationToFlow stamps `particles` on every edge and turns it off
   above PARTICLE_EDGE_CAP (40) simultaneously active edges — StatusEdge then renders
   the colored stroke without its 3 SMIL particles per edge.
7. The drawer's error banner clears when an action succeeds, so a recovered failure
   does not stay on screen.

Only `noMatches` is added to en.json here; the other locales are task B4.

Refs diegosouzapw#12392

* chore(dashboard): canvas polish i18n + changelog

Real translations for orchestration.noMatches in the 41 non-English locales,
each one written against that file's own neighbouring keys (emptyTitle,
stateRunning, searchPlaceholder) so the wording for "task" and "filter"
matches what the locale already uses. No i18n:sync-ui, no __MISSING__ left.

Adds the changelog fragment for the nine PR-B fixes.

Closes diegosouzapw#12392
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.

feat: orchestration canvas polish (fase 2 PR-A final-review minors)

1 participant