|
|
|
@@ -2258,3 +2258,106 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat
|
|
|
|
|
|
|
|
|
|
**Initial trial**
|
|
|
|
|
- For the first cut, double the current size: the `size: 12` activity-card call sites go to `size: 24`. Trial that footprint, then settle the final value in review against the control-geometry tokens.', 'done', 'medium', NULL, NULL, NULL, '2026-06-09 21:22:28', '2026-06-10 12:05:17', NULL, '1266a6e25abae600315c65aefd7d8f93', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
|
|
|
|
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2TY91VHK7TPKPMZ11EG3TM', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'ClideStatusIndicator: duplicate ''running'' key crashes AnimatedSwitcher on rapid status flips', NULL, 'backlog', 'medium', NULL, NULL, NULL, '2026-06-10 12:04:51', '2026-06-10 12:05:21', NULL, 'c6de783bb8c7c95fa548861ec4c74d4f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
|
|
|
|
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2TY91VHK7TPKPMZ11EG3TM', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'ClideStatusIndicator: duplicate ''running'' key crashes AnimatedSwitcher on rapid status flips', 'AnimatedSwitcher in ClideStatusIndicator throws "Duplicate keys found" (Stack has multiple children with key [<''running''>]) during normal app run, cascading into a flood of follow-on errors ("Tried to build dirty widget in the wrong build scope", "debugNeedsLayout is not true", "ScrollController attached to multiple scroll views", etc).
|
|
|
|
|
|
|
|
|
|
Location: lib/widgets/src/clide_status_indicator.dart:37 (AnimatedSwitcher at build()).
|
|
|
|
|
|
|
|
|
|
Root cause: each status maps to a child with a fixed ValueKey (''running'' / ''success'' / ''error''). AnimatedSwitcher cross-fades the outgoing and incoming child inside a Stack for its 200ms duration. When the status flips back to a value whose previous child is still animating out (e.g. running -> success -> running within 200ms, or repeated running rebuilds), the still-exiting child and the new child both carry ValueKey(''running'') and collide in the Stack -> duplicate-key assertion. The downstream exceptions are the framework unwinding from the failed build.
|
|
|
|
|
|
|
|
|
|
Repro: observed live during `make run` with two Claude panes bound (primary + secondary-1); status indicators flipping quickly trigger it.
|
|
|
|
|
|
|
|
|
|
Fix direction: the ValueKey must be unique per indicator instance, not just per status, so two instances (or an in-flight transition) never share a key. Options: key by status combined with a stable per-widget id, or drop the const keys and let AnimatedSwitcher key on child type. Add a widget test that rapidly toggles status within the switch duration and pumps mid-transition to guard the regression.', 'backlog', 'high', NULL, NULL, NULL, '2026-06-10 12:04:51', '2026-06-10 12:05:39', NULL, '36855b40b1014791247074c28862e0cd', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
|
|
|
|
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2TY91VHK7TPKPMZ11EG3TM', 'bug', '06FB0TNQM5TWC00GW0P3X02HZW', 'ClideStatusIndicator: duplicate ''running'' key crashes AnimatedSwitcher on rapid status flips', 'AnimatedSwitcher in ClideStatusIndicator throws "Duplicate keys found" (Stack has multiple children with key [<''running''>]) during normal app run, cascading into a flood of follow-on errors ("Tried to build dirty widget in the wrong build scope", "debugNeedsLayout is not true", "ScrollController attached to multiple scroll views", etc).
|
|
|
|
|
|
|
|
|
|
Location: lib/widgets/src/clide_status_indicator.dart:37 (AnimatedSwitcher at build()).
|
|
|
|
|
|
|
|
|
|
Root cause: each status maps to a child with a fixed ValueKey (''running'' / ''success'' / ''error''). AnimatedSwitcher cross-fades the outgoing and incoming child inside a Stack for its 200ms duration. When the status flips back to a value whose previous child is still animating out (e.g. running -> success -> running within 200ms, or repeated running rebuilds), the still-exiting child and the new child both carry ValueKey(''running'') and collide in the Stack -> duplicate-key assertion. The downstream exceptions are the framework unwinding from the failed build.
|
|
|
|
|
|
|
|
|
|
Repro: observed live during `make run` with two Claude panes bound (primary + secondary-1); status indicators flipping quickly trigger it.
|
|
|
|
|
|
|
|
|
|
Fix direction: the ValueKey must be unique per indicator instance, not just per status, so two instances (or an in-flight transition) never share a key. Options: key by status combined with a stable per-widget id, or drop the const keys and let AnimatedSwitcher key on child type. Add a widget test that rapidly toggles status within the switch duration and pumps mid-transition to guard the regression.', 'ready', 'high', NULL, NULL, NULL, '2026-06-10 12:04:51', '2026-06-10 12:07:39', NULL, '3f833daa3bb004ea42b2f0d70f6cc19b', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
|
|
|
|
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5TWC00GW0P3X02HZW', 'epic', NULL, 'UI tweaks & fixes', 'Ongoing umbrella for small, standalone UI polish, cosmetic tweaks, and visual/interaction bug fixes that don''t belong to a feature epic — color/token corrections, control placement, status surfaces, micro-interactions, and the wireframes that frame them. Children are independently shippable; the epic stays open as a rolling home for this class of work.
|
|
|
|
|
|
|
|
|
|
**PERMANENT — never close.** This is a standing rolling tracker for loose UI/UX work and bugs, not a deliverable epic. It stays open indefinitely; only its children are completed/closed. Do not mark T-276 done even when all current children are closed — new tweaks/fixes get filed here on an ongoing basis.', 'ready', 'medium', NULL, NULL, NULL, '2026-06-08 07:46:23', '2026-06-10 12:08:14', NULL, '5897237a09dcf146461d532806e0a118', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
|
|
|
|
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB0TNQM5TWC00GW0P3X02HZW', 'epic', NULL, 'UI tweaks & fixes', 'Ongoing umbrella for small, standalone UI polish, cosmetic tweaks, and visual/interaction bug fixes that don''t belong to a feature epic — color/token corrections, control placement, status surfaces, micro-interactions, and the wireframes that frame them. Children are independently shippable; the epic stays open as a rolling home for this class of work.
|
|
|
|
|
|
|
|
|
|
**PERMANENT — never close.** This is a standing rolling tracker for loose UI/UX work and bugs, not a deliverable epic. It stays open indefinitely; only its children are completed/closed. Do not mark T-276 done even when all current children are closed — new tweaks/fixes get filed here on an ongoing basis.', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-08 07:46:23', '2026-06-10 12:08:18', NULL, 'bb9d23c8e18d23c95ddbcdf7595f9b35', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
|
|
|
|
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2W4G9K8ZF782W7H2TM5XA8', 'story', '06FB0TNQM5TWC00GW0P3X02HZW', 'Hover ''pick up ticket'' run-icon that injects the full ticket into the focused Claude pane via the message bus', 'On ticket-card mouseover in the tickets sidebar, surface a small `person-simple-run` icon that, when clicked, hands the full ticket to the currently-focused Claude conversation panel as a "pick this up and start processing it" prompt. Routed over the message bus, not by reaching into the session orchestrator directly.
|
|
|
|
|
|
|
|
|
|
**Interaction**
|
|
|
|
|
- Hover a ticket card -> a `person-simple-run` action icon fades in (top-right of the card, where the green-arrow mock points).
|
|
|
|
|
- Click -> the focused Claude pane receives an injected user prompt that contains the full ticket (all fields, as the context/detail pane renders it) plus a short instruction to begin working it.
|
|
|
|
|
|
|
|
|
|
**Where (file:line)**
|
|
|
|
|
- Card + hover: `_TicketCard` in lib/builtin/tickets/src/tickets_view.dart:226-304. `ClideTappable` (line 242) already exposes a `hovered` bool in its builder (line 244) — gate the icon''s visibility on that. No new MouseRegion needed.
|
|
|
|
|
- Icon: `PhosphorIcons.byName(''person-simple-run'')` (lib/widgets/src/icons/phosphor.dart:49). Confirmed present in phosphor_glyphs.g.dart (0xe730).
|
|
|
|
|
- Full-ticket payload: fetch via the same path the context pane uses — `pql.tickets.show` with `withContext:true` (ticket_detail_controller.dart:45-49) -> TicketDetail (id/title/type/status/priority/description/parentId/decisionRef/assignedTo, ticket_detail_controller.dart:12-20). No serializer exists yet; build the prompt text from these fields (same content the detail view renders, ticket_detail_view.dart:59-127).
|
|
|
|
|
|
|
|
|
|
**Use the message bus (required)**
|
|
|
|
|
- Do NOT call ClaudeSessionOrchestrator.injectMessage directly from the sidebar. Publish on the bus instead, matching the existing tickets pattern: `messages.publish(''builtin.tickets'', ''<channel>'', {...})` (see ticket_detail_controller.dart:62 publishing ''focus''; bus lives on the kernel facade as `messages`, facade.dart:87/144).
|
|
|
|
|
- Proposed: publish `(''builtin.tickets'', ''pick-up'', { ''id'': T-NNN, ''prompt'': <full ticket + instruction text> })`.
|
|
|
|
|
- The Claude builtin subscribes to that channel and injects into the focused session — it resolves the focused pane via focus.activeContributionId (focus.dart:27-28) and routes through the orchestrator (session_orchestrator.dart:331-336 injectMessage / stream_json_session.dart:635 send). This keeps the sidebar decoupled from Claude internals and honors the bus-for-interaction rule.
|
|
|
|
|
|
|
|
|
|
**Edge cases (settle in review)**
|
|
|
|
|
- No focused Claude pane (focus.activeContributionId null / non-Claude pane focused): no-op with a toast, or fall back to ''primary''? Decide in review.
|
|
|
|
|
- Busy session (mid-turn): queue the inject vs. block with a hint.
|
|
|
|
|
- Prompt shape: confirm the exact wording/format of the injected message (full ticket markdown + a one-line "start processing this ticket" lead-in).
|
|
|
|
|
|
|
|
|
|
**Acceptance**
|
|
|
|
|
- Hovering a sidebar ticket card reveals a `person-simple-run` icon; it is hidden when not hovered.
|
|
|
|
|
- Clicking it publishes a single message-bus event carrying the full ticket; the focused Claude pane receives an injected prompt containing all ticket fields and an instruction to begin.
|
|
|
|
|
- The sidebar code does not import or call the session orchestrator directly — interaction is bus-only.
|
|
|
|
|
- Clicking with no focused Claude pane degrades gracefully (no crash).', 'backlog', 'medium', NULL, NULL, NULL, '2026-06-10 12:10:04', '2026-06-10 12:10:04', NULL, 'a23c4f546ae8408988106e0c98fcac03', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
|
|
|
|
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB2W4G9K8ZF782W7H2TM5XA8', 'story', '06FB0TNQM5TWC00GW0P3X02HZW', 'Hover ''pick up ticket'' run-icon that injects the full ticket into the focused Claude pane via the message bus', 'On ticket-card mouseover in the tickets sidebar, surface a small `person-simple-run` icon that, when clicked, hands the full ticket to the currently-focused Claude conversation panel as a "pick this up and start processing it" prompt. Routed over the message bus, not by reaching into the session orchestrator directly.
|
|
|
|
|
|
|
|
|
|
**Interaction**
|
|
|
|
|
- Hover a ticket card -> a `person-simple-run` action icon fades in (top-right of the card, where the green-arrow mock points).
|
|
|
|
|
- Click -> the focused Claude pane receives an injected user prompt that contains the full ticket (all fields, as the context/detail pane renders it) plus a short instruction to begin working it.
|
|
|
|
|
|
|
|
|
|
**Where (file:line)**
|
|
|
|
|
- Card + hover: `_TicketCard` in lib/builtin/tickets/src/tickets_view.dart:226-304. `ClideTappable` (line 242) already exposes a `hovered` bool in its builder (line 244) — gate the icon''s visibility on that. No new MouseRegion needed.
|
|
|
|
|
- Icon: `PhosphorIcons.byName(''person-simple-run'')` (lib/widgets/src/icons/phosphor.dart:49). Confirmed present in phosphor_glyphs.g.dart (0xe730).
|
|
|
|
|
- Full-ticket payload: fetch via the same path the context pane uses — `pql.tickets.show` with `withContext:true` (ticket_detail_controller.dart:45-49) -> TicketDetail (id/title/type/status/priority/description/parentId/decisionRef/assignedTo, ticket_detail_controller.dart:12-20). No serializer exists yet; build the prompt text from these fields (same content the detail view renders, ticket_detail_view.dart:59-127).
|
|
|
|
|
|
|
|
|
|
**Use the message bus (required)**
|
|
|
|
|
- Do NOT call ClaudeSessionOrchestrator.injectMessage directly from the sidebar. Publish on the bus instead, matching the existing tickets pattern: `messages.publish(''builtin.tickets'', ''<channel>'', {...})` (see ticket_detail_controller.dart:62 publishing ''focus''; bus lives on the kernel facade as `messages`, facade.dart:87/144).
|
|
|
|
|
- Proposed: publish `(''builtin.tickets'', ''pick-up'', { ''id'': T-NNN, ''prompt'': <full ticket + instruction text> })`.
|
|
|
|
|
- The Claude builtin subscribes to that channel and injects into the focused session — it resolves the focused pane via focus.activeContributionId (focus.dart:27-28) and routes through the orchestrator (session_orchestrator.dart:331-336 injectMessage / stream_json_session.dart:635 send). This keeps the sidebar decoupled from Claude internals and honors the bus-for-interaction rule.
|
|
|
|
|
|
|
|
|
|
**Edge cases (settle in review)**
|
|
|
|
|
- No focused Claude pane (focus.activeContributionId null / non-Claude pane focused): no-op with a toast, or fall back to ''primary''? Decide in review.
|
|
|
|
|
- Busy session (mid-turn): queue the inject vs. block with a hint.
|
|
|
|
|
- Prompt shape: confirm the exact wording/format of the injected message (full ticket markdown + a one-line "start processing this ticket" lead-in).
|
|
|
|
|
|
|
|
|
|
**Acceptance**
|
|
|
|
|
- Hovering a sidebar ticket card reveals a `person-simple-run` icon; it is hidden when not hovered.
|
|
|
|
|
- Clicking it publishes a single message-bus event carrying the full ticket; the focused Claude pane receives an injected prompt containing all ticket fields and an instruction to begin.
|
|
|
|
|
- The sidebar code does not import or call the session orchestrator directly — interaction is bus-only.
|
|
|
|
|
- Clicking with no focused Claude pane degrades gracefully (no crash).', 'ready', 'medium', NULL, NULL, NULL, '2026-06-10 12:10:04', '2026-06-10 12:12:30', NULL, '0847120e70ae275e3e115478d092c283', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
|
|
|
|
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06FB1XDWKQ594ET4GDYEFK5ZJ4', 'story', '06FB0TNQM5TWC00GW0P3X02HZW', 'Permission card: add ''Deny & simplify'' option that denies with a retry-simpler note', 'Add a fourth option to the Claude permission prompt card (lib/builtin/claude/src/prompt_card.dart) alongside Allow / Allow & don''t ask again / Deny.
|
|
|
|
|
|
|
|
|
|
WHAT IT DOES
|
|
|
|
|
A deny that carries a preformatted follow-up note telling Claude the action was too complex for the permission system and to retry in a simpler format. Implemented as a _permDeny variant that passes a fixed note into DenyTool (today _permDeny uses _permNote() ?? ''Denied by the user.'' at prompt_card.dart:202). The user''s own note field, if filled, should still be respected — append it to / combine with the preformatted text rather than discard it.
|
|
|
|
|
|
|
|
|
|
LABEL: ''Deny & simplify'' (working label; placed after Deny).
|
|
|
|
|
TOOLTIP: ClideButton already supports (clide_button.dart:26) — add a mouseover explaining the behavior, e.g. ''Deny and ask Claude to retry this action in a simpler format — complex interactions don''t work well with the permission system.''
|
|
|
|
|
|
|
|
|
|
PREFORMATTED DENY NOTE (workshop wording, starting point):
|
|
|
|
|
"Denied — this action is too complex for the permission system to approve cleanly. Please retry with a simpler, more granular approach (break it into smaller steps or use a plainer command) to avoid this permission prompt. This is a one-off for THIS action only: do not add a memory and do not change permission settings — just reformulate and try again."
|
|
|
|
|
The ''do not add a memory / do not change permission settings'' clause is deliberate: without it Claude tends to ''fix'' the permission system (writing memories, rewriting permission config), which means continuous fiddling with a surface we don''t want it touching.
|
|
|
|
|
|
|
|
|
|
NUMBER-KEY SLOT
|
|
|
|
|
_activateNumber (prompt_card.dart:161-164) maps 1=Allow, 2=Allow&remember (when canRemember), 3/2=Deny. Add the new option as the next index (4 when canRemember, else 3). Keep Deny as its own option; the new one is additive. Update the digit/numpad shortcut mapping accordingly (see also T-310 numpad parity).
|
|
|
|
|
|
|
|
|
|
DESIGN NOTE — escalation behavior
|
|
|
|
|
The deny note enters the conversation and stays in context, so repeated use within one session compounds (Claude leans progressively harder toward simpler formats). That''s largely the intended escalating pressure, but the note is phrased as a one-shot ''retry THIS action'' rather than a standing rule to limit over-correction. Worth watching in testing whether repeated denials over-bias toward trivial formats.
|
|
|
|
|
|
|
|
|
|
ACCEPTANCE
|
|
|
|
|
- A fourth button ''Deny & simplify'' appears on the permission card with a tooltip.
|
|
|
|
|
- Activating it resolves the prompt as a deny whose note is the preformatted retry-simpler text (with the user''s typed note appended when present).
|
|
|
|
|
- The note explicitly tells Claude not to add a memory or change permission settings.
|
|
|
|
|
- Number-row and numpad digit shortcuts address the new option in the correct slot.
|
|
|
|
|
- Widget test covers the new button resolving to a DenyTool with the expected note.
|
|
|
|
|
|
|
|
|
|
CLARIFICATION (the TOOLTIP line above lost a word to shell escaping): ClideButton already exposes a tooltip parameter (clide_button.dart:17,26,42) — pass tooltip on the new button for the mouseover; no widget change needed.', 'done', 'medium', NULL, NULL, NULL, '2026-06-10 09:55:55', '2026-06-10 12:14:00', NULL, '995bc6dbbc7231306303b838aba8a2eb', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at > tickets.updated_at OR (excluded.updated_at = tickets.updated_at AND excluded.hash > tickets.hash);
|
|
|
|
|