Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion WORKLOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
# Worklog

## 2026-06-22

### nx2 chat — plan card UX polish + task-status persistence fixes

**Task status persistence across tool boundaries** (`chat.js`):
- `TEXT_END` fires after each text segment between tool calls, splitting output into multiple string messages per inter-tool boundary. Task-item directives for step N live in a different message than step N+1, so the previous code (passing only the last message) missed earlier `done` directives. Fixed by concatenating all completed assistant text messages before passing to `mergeTaskItemsFromText`.

**Plan card renderer fixes** (`renderers.js`):
- Switched `renderSubmitPlanCard` and `renderApprovalCard` from `document.createElement` to Lit `html\`\`` templates; `document.createElement` recreated the element each render, losing `_expanded` state.
- `renderMessageContent`: returns `nothing` for `TASK_ITEM` directives (previously rendered as visible text); filters `nothing` items; returns `nothing` if all items suppressed.
- `renderAssistantMessage`: skips message wrapper when content is `nothing` (eliminates empty `<div class="message message-assistant">` gaps in the DOM).
- Removed unused `renderTaskItemDirective` function.

**Plan card template restructure** (`campaign-plan-card.js`):
- `_expanded` defaults to `true`.
- `showCollapsed = !this._expanded` (was: only collapsed when running AND not expanded).
- Moved title/description out of `.plan-header` into a new `.plan-body` div below the header strip.
- Header strip is now 48px fixed-height with border-bottom, matching Figma spec.

**CSS fixes** (`campaign-plan-card.css`, `task-item.css`):
- All `--s2-spacing-150` (undefined) replaced with `--s2-spacing-200` (12px).
- All `--s2-spacing-115` replaced with `--s2-spacing-100` (8px).
- `.plan-card` gap: 12px; `.plan-header`: height 48px, border-bottom, padding 12px.
- `.plan-body`: padding 12px 16px, gap 8px.
- `.plan-tasks`: background `--s2-gray-50`, border `1px solid --s2-gray-200`, border-radius 8px, margin with side 16px offset.
- `.plan-btn`: height 24px, padding `0 16px`, weight 400.
- `.plan-btn-primary`: color `--s2-static-white` (was `--s2-gray-25` which flips to near-black in dark mode).
- `task-item.css`: `.task-label` margin-left `--s2-spacing-100`; `:host` gap `--s2-spacing-200`.

## 2026-06-26

### nx2/blocks/chat/chat.js — skill selection preserves pending attachments (feat/da-skill-attachment-fix)
Expand All @@ -14,7 +43,6 @@ Post-review follow-up (fe049a9b):
- Renamed loop variable `i` → `item` in `_onSlashSelect` callbacks
- Added regression tests in `test/nx2/blocks/chat/chat.test.js` (8 tests, all pass)


## 2026-06-23

### nx2/blocks/shared/dialog — configurable panel sizing (dialog-css-vars branch)
Expand Down
2 changes: 1 addition & 1 deletion nx2/blocks/chat/chat-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function affectedFolders(toolName, input) {
}

const AGENT_URL = new URLSearchParams(window.location.search).get('ref') === 'local'
? 'http://localhost:4200/chat'
? 'http://localhost:4002/chat'
: 'https://agent.da.live/chat';

/**
Expand Down
24 changes: 23 additions & 1 deletion nx2/blocks/chat/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import { renderMessage, renderApprovalCard } from './renderers.js';
import './welcome/welcome.js';
import './prompts/prompts.js';
import './pills/pills.js';
import './messages/campaign-plan-card.js';
import './messages/preflight-card.js';
import './messages/task-list.js';
import './messages/task-item.js';
import { loadSiteConfig } from './utils/api.js';
import { ADOBE_AI_GUIDELINES_URL, ADD_MENU_ITEMS, MENU_OPTIONS, ROLE, TOOL_STATE } from './constants.js';
import { getConfig } from '../../scripts/nx.js';
Expand Down Expand Up @@ -494,6 +498,24 @@ class NxChat extends LitElement {
await this._onFilesSelected(accepted);
}

get _taskText() {
const msgs = this.messages ?? [];
const last = msgs.at(-1);
const streamingText = last?.streaming ? last.content : null;
if (streamingText) return streamingText;
// TEXT_END splits output into one string message per inter-tool segment, so
// task-item directives for step N may live in a different message than step N+1.
// Concatenate all assistant text to let mergeTaskItemsFromText find them all.
return msgs
.filter((m) => m.role === ROLE.ASSISTANT && typeof m.content === 'string' && !m.streaming)
.map((m) => m.content)
.join('\n') || null;
}

_renderMessages() {
return (this.messages ?? []).map((msg) => renderMessage(msg, this.toolCards, this._taskText));
}

render() {
const { view } = this._context ?? {};
const prompts = (this._prompts ?? [])
Expand Down Expand Up @@ -530,7 +552,7 @@ class NxChat extends LitElement {
@nx-show-prompts=${this._openPrompts}
></nx-chat-welcome>`
: nothing}
${this.messages?.map((msg) => renderMessage(msg, this.toolCards))}
${this._renderMessages()}
${this.thinking && !this.messages?.at(-1)?.streaming ? html`<div class="chat-thinking">Thinking...</div>` : nothing}
</div>
</div>
Expand Down
29 changes: 29 additions & 0 deletions nx2/blocks/chat/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ const TOOL_NAME = {
CONTENT_MOVE: 'content_move',
CONTENT_UPDATE: 'content_update',
CONTENT_UPLOAD: 'content_upload',
ENTER_PLAN_MODE: 'enter_plan_mode',
EXIT_PLAN_MODE: 'exit_plan_mode',
RUN_PREFLIGHT: 'run_preflight',
};

/**
Expand Down Expand Up @@ -88,12 +91,38 @@ const ROLE = {
TOOL: 'tool',
};

/**
* Directive fence types that map to rich interactive renderer components.
* These are emitted by da-agent inside :::type ... ::: fences in text-delta events.
*/
const DIRECTIVE_TYPE = {
PLAN: 'plan',
TASK_LIST: 'task-list',
TASK_ITEM: 'task-item',
PREFLIGHT: 'preflight',
};

/**
* Task status values shared across plan/task-list/task-item components.
* Matches values sent in :::plan / :::task-list / :::task-item directive payloads.
*/
const TASK_STATUS = {
PENDING: 'pending',
RUNNING: 'running',
DONE: 'done',
};

const PLAN_RUN_EVENT = 'nx-plan-run';

export {
ADOBE_AI_GUIDELINES_URL,
ADD_MENU_ITEMS,
AGENT_EVENT,
DIRECTIVE_TYPE,
MENU_OPTIONS,
PLAN_RUN_EVENT,
ROLE,
TASK_STATUS,
TOOL_INPUT,
TOOL_NAME,
TOOL_SCOPE,
Expand Down
181 changes: 181 additions & 0 deletions nx2/blocks/chat/messages/campaign-plan-card.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
:host {
display: block;
font-family: var(--s2-font-family);
}

.plan-card {
background: var(--s2-gray-25);
border-radius: var(--s2-corner-radius-400);
box-shadow: 0 1px 4px 0 color-mix(in srgb, var(--s2-gray-800) 12%, transparent);
overflow: hidden;
display: flex;
flex-direction: column;
gap: var(--s2-spacing-200);
margin-top: var(--s2-spacing-200);
margin-bottom: var(--s2-spacing-75);
}

/* ── Header strip (48px, border-bottom) ── */

.plan-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 48px;
padding: var(--s2-spacing-200);
border-bottom: 1px solid var(--s2-gray-200, #e1e1e1);
box-sizing: border-box;
}

.plan-type-label {
font-size: var(--s2-body-size-xs);
color: var(--s2-gray-600);
display: flex;
align-items: center;
gap: var(--s2-spacing-75);
}

/* ── Title / description area ── */

.plan-body {
display: flex;
flex-direction: column;
gap: var(--s2-spacing-100);
padding: var(--s2-spacing-200) var(--s2-spacing-300);
}

.plan-type-icon {
width: 14px;
height: 14px;
display: inline-block;
flex-shrink: 0;
background-color: currentcolor;
mask-image: url("/img/icons/s2-icon-aichat-20-n.svg");
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
}

.plan-title {
font-size: var(--s2-font-size-200, 18px);
font-weight: 700;
line-height: var(--s2-line-height-200, 1.3);
letter-spacing: var(--s2-letter-spacing, normal);
color: var(--s2-gray-900);
margin: 0;
}

.plan-description {
font-size: var(--s2-body-size-s);
color: var(--s2-gray-700);
margin: 0;
line-height: 1.4;
}

.plan-header-actions {
display: flex;
align-items: center;
gap: var(--s2-spacing-75);
flex-shrink: 0;
}

.plan-icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: var(--s2-corner-radius-300);
border: none;
background: transparent;
color: var(--s2-gray-700);
cursor: pointer;
padding: 0;

&:hover {
background-color: var(--s2-gray-100);
}
}

.plan-chevron-icon {
width: 16px;
height: 16px;
display: block;
transition: transform 0.2s ease;
}

.plan-icon-btn-expanded .plan-chevron-icon {
transform: rotate(180deg);
}

/* ── Task area ── */

.plan-tasks {
display: flex;
flex-direction: column;
gap: var(--s2-spacing-200);
padding: var(--s2-spacing-200);
margin: 0 var(--s2-spacing-300) var(--s2-spacing-200);
background-color: var(--s2-gray-50, #f8f8f8);
border-radius: var(--s2-corner-radius-500);
border: 1px solid var(--s2-gray-200, #e1e1e1);
}

.plan-tasks-header {
font-size: var(--s2-body-size-xs);
color: var(--s2-gray-600);
margin-bottom: var(--s2-spacing-50);
}

.plan-tasks-progress {
font-size: var(--s2-body-size-xs);
color: var(--s2-gray-600);
font-variant-numeric: tabular-nums;
}

.plan-task-row {
display: flex;
align-items: center;
gap: var(--s2-spacing-200);
}

/* ── Buttons ── */

.plan-btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 24px;
padding: 0 var(--s2-spacing-300);
border-radius: var(--s2-corner-radius-300);
font-family: var(--s2-font-family);
font-size: var(--s2-body-size-s);
font-weight: 400;
cursor: pointer;
border: none;
white-space: nowrap;

&:disabled {
opacity: 0.55;
cursor: not-allowed;
}
}

.plan-btn-ghost {
background: transparent;
color: var(--s2-gray-1000);
border: 1px solid var(--s2-gray-300);

&:hover:not(:disabled) {
background: var(--s2-gray-75);
}
}

.plan-btn-primary {
background: var(--s2-blue-900);
color: var(--s2-static-white, #fff);

&:hover:not(:disabled) {
background: var(--s2-blue-1000);
}
}
Loading
Loading