fix(web): keep markdown table headers readable on narrow content (#1122)
This commit is contained in:
@@ -1,107 +0,0 @@
|
||||
---
|
||||
name: pull-upstream
|
||||
description: Pull latest changes from the public kandev repo, rebase onto them, and fix any conflicts or issues.
|
||||
---
|
||||
|
||||
# Pull Upstream
|
||||
|
||||
Pull latest changes from the public kandev repo, rebase onto them, and fix any conflicts or issues.
|
||||
|
||||
## Context
|
||||
|
||||
This repo (`kdlbs/kandev-orchestrate`) is a private fork of `kdlbs/kandev` (public).
|
||||
|
||||
```
|
||||
origin → git@github.com:kdlbs/kandev-orchestrate.git (private, our code)
|
||||
upstream → git@github.com:kdlbs/kandev.git (public, community changes)
|
||||
```
|
||||
|
||||
The public repo's `main` receives bug fixes, features, and community PRs independently.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Fetch upstream
|
||||
|
||||
```bash
|
||||
git fetch upstream
|
||||
```
|
||||
|
||||
### 2. Check what's new
|
||||
|
||||
```bash
|
||||
git log --oneline main..upstream/main
|
||||
```
|
||||
|
||||
If empty, nothing to do. Report and stop.
|
||||
|
||||
### 3. Update main
|
||||
|
||||
Fast-forward `main` to match upstream, then push:
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git merge --ff-only upstream/main
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### 4. Rebase feature branch
|
||||
|
||||
Switch back to the feature branch and rebase onto updated main:
|
||||
|
||||
```bash
|
||||
git checkout feature/orchestrate
|
||||
git rebase main
|
||||
```
|
||||
|
||||
### 5. Handle conflicts
|
||||
|
||||
If the rebase has conflicts:
|
||||
|
||||
1. For each conflicting file, read both sides and resolve correctly
|
||||
2. Our office feature code takes priority over upstream when the same area is modified
|
||||
3. Upstream changes to shared infrastructure (agent lifecycle, MCP, task system, workflow engine) should be preserved — integrate both sides
|
||||
4. After resolving each file: `git add <file>`
|
||||
5. Continue: `git rebase --continue`
|
||||
6. Repeat until rebase completes
|
||||
|
||||
Common conflict zones:
|
||||
- `cmd/kandev/main.go` — we wire office services here
|
||||
- `cmd/kandev/helpers.go` — we register office routes here
|
||||
- `internal/mcp/server/server.go` — we added ModeOffice
|
||||
- `internal/mcp/handlers/handlers.go` — we added office MCP tools
|
||||
- `apps/web/lib/state/` — we added office store slices
|
||||
- `docs/specs/INDEX.md` — both sides add spec entries
|
||||
- `CLAUDE.md` — may have parallel edits
|
||||
|
||||
### 6. Verify
|
||||
|
||||
Run the full verification pipeline. Use subshells so the second command
|
||||
doesn't resolve `cd apps` relative to the first command's cwd (which
|
||||
would look for a non-existent `apps/backend/apps/`):
|
||||
|
||||
```bash
|
||||
(cd apps/backend && go build ./... && make test && make lint)
|
||||
(cd apps && pnpm --filter @kandev/web lint)
|
||||
```
|
||||
|
||||
Fix any issues:
|
||||
- **Import conflicts**: upstream may have renamed/moved packages (e.g. `agentctl/server/mcp` → `mcp/server`)
|
||||
- **Interface changes**: upstream may have changed interfaces we implement
|
||||
- **Test failures**: upstream tests may depend on state we changed
|
||||
- **Lint errors**: upstream may have added stricter rules
|
||||
|
||||
### 7. Push
|
||||
|
||||
Force-push the rebased feature branch:
|
||||
|
||||
```bash
|
||||
git push --force-with-lease
|
||||
```
|
||||
|
||||
### 8. Report
|
||||
|
||||
Summarize:
|
||||
- How many upstream commits were pulled
|
||||
- Any conflicts resolved and how
|
||||
- Any fixes applied post-rebase
|
||||
- Build/test/lint status
|
||||
@@ -216,3 +216,4 @@ When you hit a limit, extract a helper function. Prefer composition over growing
|
||||
- `internal/agentctl/AGENTS.md` — agentctl server route groups, adapter model, ACP protocol
|
||||
- `internal/agentctl/server/api/AGENTS.md` — reverse-proxy body rewriting (`Accept-Encoding`), iframe-blocking header stripping
|
||||
- `internal/integrations/AGENTS.md` — playbook for adding a new third-party integration (Jira/Linear pattern)
|
||||
- `cmd/mock-agent/AGENTS.md` — predefined `/e2e:<name>` scenarios vs inline `e2e:...` scripts; recipe for adding a scenario to reproduce agent-output rendering bugs locally
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# mock-agent — scenario library for reproducing agent output locally
|
||||
|
||||
Scoped guidance for `apps/backend/cmd/mock-agent/`. The mock agent satisfies the same ACP surface a real agent does, so the rest of the stack (orchestrator, lifecycle manager, agentctl, web UI) sees it as an ordinary agent. It runs in dev/e2e mode behind `KANDEV_MOCK_AGENT=true` (set automatically by the `dev` and `e2e` profiles — see `profiles.yaml`).
|
||||
|
||||
## Two ways to drive it
|
||||
|
||||
**1. Predefined scenarios — `/e2e:<name>`**
|
||||
|
||||
`handler.go` routes any prompt starting with `/e2e:` to `emitPredefinedScenario(e, name)`, which looks `name` up in `scenarioRegistry` (`scenarios.go`). The registry is the single source of truth for scenario names — to add a scenario, add one map entry and one `scenario<Name>(e *emitter)` function.
|
||||
|
||||
Friendly aliases live in `handler.go` next to the dispatcher: `/ask-single`, `/ask-multiple`, `/crash`, `/todo`, `/mermaid`, `/markdown`, `/sleep [n]`, `/tool:<name>`, `/subagent`, `/subtask`.
|
||||
|
||||
**2. Inline scripts — `e2e:<directive>(...)`**
|
||||
|
||||
`script.go` parses prompts starting with `e2e:` (no leading slash) as multi-line scripts. Supported directives include `e2e:message(...)`, `e2e:thinking(...)`, `e2e:tool_use("Name", {...})`, `e2e:tool_result(...)`, `e2e:delay(ms)`, and the MCP-flavoured `e2e:mcp:kandev:<tool>({...})`. Comments start with `#`, blank lines are ignored. Tests live in `script_test.go`.
|
||||
|
||||
Use predefined scenarios for anything you want to trigger from the slash menu or replay across sessions; use inline scripts for ad-hoc shapes inside a single E2E spec or manual session.
|
||||
|
||||
## Adding a scenario to reproduce a UI rendering bug
|
||||
|
||||
When a real-agent message renders poorly (markdown table, tool card, diff panel, plan summary, etc.), the cheapest reliable repro is a mock scenario that emits a representative payload. Pattern:
|
||||
|
||||
1. Add `"<your-name>": scenario<YourName>` to `scenarioRegistry` in `scenarios.go`.
|
||||
2. Implement `scenario<YourName>(e *emitter)` using the helpers in `emitter.go` — `e.text`, `e.thought`, `e.startTool` / `e.completeTool`, `e.plan`, etc. Keep delays modest (`fixedDelay(100)`) so manual repro feels live but tests stay fast.
|
||||
3. Trigger from any task in dev/mock mode by typing `/e2e:<your-name>` as the prompt.
|
||||
|
||||
This avoids burning real-agent tokens, gives you a deterministic payload to iterate against, and the scenario stays around as a permanent regression hook — anyone can re-trigger it after the fix.
|
||||
|
||||
## Emitter helpers worth knowing
|
||||
|
||||
`emitter.go` wraps the ACP `sessionUpdater` with helpers that shield scenarios from SDK plumbing:
|
||||
|
||||
- `e.text(s)` / `e.thought(s)` — plain agent message vs reasoning channel.
|
||||
- `e.startTool(id, title, kind, input, locs...)` / `e.completeTool(id, output)` — paired tool-call lifecycle.
|
||||
- `e.plan(entries)` — ACP plan updates.
|
||||
- `e.startMonitorTool(id, taskID, command)` / `e.emitMonitorEvent(taskID, body)` / `e.endMonitorTool(id)` — reproduces the two-frame Monitor wire pattern the kandev ACP adapter recognises. Use these instead of building raw `SessionNotification`s when you need to test the Monitor recogniser path; they embed the correct `_meta.claudeCode.toolName=Monitor` shape.
|
||||
- `e.requestPermission(...)` — interactive permission flow for scenarios that need an Allow/Reject decision.
|
||||
|
||||
## Tests
|
||||
|
||||
`mock_agent_test.go` exercises the top-level handler dispatch. `script_test.go` covers the inline-script parser end-to-end. New scenarios don't typically need a unit test — the assertion is that the scenario name is registered in `scenarioRegistry` and the payload looks right when triggered manually. If a scenario contains conditional logic worth pinning, add a focused test that constructs an emitter with a fake `sessionUpdater` and asserts on the emitted notifications.
|
||||
@@ -37,6 +37,7 @@ var scenarioRegistry = map[string]func(e *emitter){
|
||||
"kandev-mcp-permission": scenarioKandevMCPPermission,
|
||||
"review-cumulative-setup": scenarioReviewCumulativeSetup,
|
||||
"symlink-file-setup": scenarioSymlinkFileSetup,
|
||||
"markdown-table": scenarioMarkdownTable,
|
||||
}
|
||||
|
||||
// emitPredefinedScenario dispatches to a named e2e scenario.
|
||||
@@ -813,3 +814,21 @@ func makeGitRunner(wd string) func(args ...string) error {
|
||||
func contextWithTimeout(seconds int) (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), time.Duration(seconds)*time.Second)
|
||||
}
|
||||
|
||||
// scenarioMarkdownTable: emits a dense label/value markdown table modeled on a
|
||||
// real agent bug-report summary. The table has narrow header labels in column 1
|
||||
// and long inline-code identifiers in column 2 — the shape that previously
|
||||
// caused header words to wrap character-by-character ("Failin g test") because
|
||||
// the value column starved the label column.
|
||||
func scenarioMarkdownTable(e *emitter) {
|
||||
fixedDelay(100)
|
||||
e.text("Pushed.\n\n" +
|
||||
"## Summary\n\n" +
|
||||
"| | |\n" +
|
||||
"|---|---|\n" +
|
||||
"| **Failing test** | `TestHandleAgentBootReady_DrainsOrphanedQueuedMessage/already_WAITING_FOR_INPUT_(boot_raced_persistResumeState)` |\n" +
|
||||
"| **Symptom** | `session.State = \"RUNNING\", want WAITING_FOR_INPUT` |\n" +
|
||||
"| **Root cause** | Pre-existing race, not introduced by this PR. `handleAgentBootReady` synchronously flips state to `WAITING_FOR_INPUT` then spawns a goroutine that calls `PromptTask` → flips state to `RUNNING`. The test asserted on `WAITING_FOR_INPUT` immediately after the handler returned — on faster CI scheduling the goroutine wins the race. The kandev-ci container apparently schedules tighter than the github-hosted ubuntu, so it loses where the previous env got lucky. |\n" +
|
||||
"| **Fix** | Cherry-picked `b8d06ea8 test(backend): fix race in TestHandleAgentBootReady_DrainsOrphanedQueuedMessage` from `feature/subtask-with-repo-se-vhz` (a parallel branch that already addressed this). The test now accepts either `WAITING_FOR_INPUT` or `RUNNING` — both prove the boot-ready flip landed and rule out the original `STARTING + queue still full` regression. |\n" +
|
||||
"| **Local verification** | `go test -race -count=5` → 5/5 PASS. |\n")
|
||||
}
|
||||
|
||||
@@ -775,8 +775,13 @@
|
||||
|
||||
.markdown-body table {
|
||||
border-collapse: collapse;
|
||||
/* width:100% fills the container and pins the table to it, so it can never
|
||||
shrink-to-fit and grow into the surrounding overflow-x-auto scroll
|
||||
container (which is what produced a horizontal scrollbar under width:auto).
|
||||
Long values wrap instead of overflowing. */
|
||||
width: 100%;
|
||||
margin: 0.75rem 0;
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
.markdown-body th,
|
||||
@@ -784,11 +789,31 @@
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 13px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.markdown-body td {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.markdown-body th {
|
||||
background: var(--muted);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Label/value tables (common in agent output) put short keys in the first
|
||||
column. Keep that column on one line and sized to its content so a long
|
||||
value cell can't starve it down to a few characters and wrap "Failing"
|
||||
into "Failin g". */
|
||||
.markdown-body td:first-child {
|
||||
white-space: nowrap;
|
||||
width: 1%;
|
||||
}
|
||||
|
||||
.markdown-body td:not(:first-child) code {
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.markdown-body thead {
|
||||
|
||||
Reference in New Issue
Block a user