Repository navigation
client: a connector's failure text is one click to copy - #2028
Merged
Merged
Conversation
…owLab#2027) A row in the error state can carry 2,000 characters of the host's own failure text, but its only actions were Try again and Disconnect, so passing that sentence on meant hand-selecting it. Copy error puts the stored text on the clipboard through copiedToClipboard() and says on the button whether it landed. Fixes ThinkFlowLab#2027 Written-by: Dev-1 Owner: Dev-1
HugoXK
force-pushed
the
dev/1-connector-error-copy
branch
from
November 1, 2026 11:31
b75e5c5 to
c9b3d90
Compare
Collaborator
|
Verdict: PASS at c9b3d90 Ran at this tip: Red first: putting only the two changed sources back to their base blobs (938a1ec) turns all four cases red; the test file is new, so nothing test-side can hide the change. What I checked:
Metadata: added |
This was referenced Nov 1, 2026
HugoXK
added a commit
to HugoXK/FlyBot
that referenced
this pull request
Nov 1, 2026
… after three (ThinkFlowLab#2032) Owner: Dev-3 Reviewer: Birfy Fixes ThinkFlowLab#2030 ## What changed The details panel's **Scheduled tasks** line drew `tasks.slice(0, 3)` and said nothing about what it left out: a Bot with five scheduled tasks looked like a Bot with three, and the only way to learn the real number was to open Settings → Scheduled tasks by hand. - Above three tasks the section title carries the count — `Scheduled tasks · 5`, the same `·` separator the settings rows and the board already use. At three or fewer the title stays exactly `Scheduled tasks`, so the common case does not change. - One row after the three says how many are left (`2 more not shown`, the key the project board already uses) and opens the full list. - That row calls `openSettings('schedules')` with **no bot id**: `schedules.js` is `useState(Boolean(start))` and the settings page is `start: id ?? undefined`, so an id opens the *new-task form* instead of the list. Only the empty state's button passes `bot.id`, which is what "Add one" means. - Zero tasks: the empty state is untouched — same label, same `openSettings('schedules', bot.id)`. Scope: `ScheduleSummary` only. `SchedulesPage`, `TaskList`, the settings section, the cap of three, the row order and `nextText` are untouched; no new i18n string beyond the count-bearing title (the `N more` row reuses an existing key); one changelog fragment and one sentence on the owned subsystem doc (`core/src/client/` is an owned path). ## Rebase The branch was rebased onto `f6fcb7ffabee43da79388313cbcdb7ada9111dcb` (the merge of ThinkFlowLab#2028, which had also touched `core/client.js` and both generated patches) and every generated artifact was **rebuilt on that base**, not text-merged: - `core/scripts/build-client.mjs` wrote `core/client.js`; - `node scripts/ds-bot-patches.mjs generate` ran twice — the second run left `core/client.js`, `patches/ds-bot/0001-core-fixes.patch` and `patches/ds-bot/0008-client-bundle.patch` byte-identical (md5 `cc779a1bc78475c33e13028dc7294779`, `c3df40f0e46bee10deabc6c8ed4a7b3c`, `45aa8be6c2125fadf1a2ad87173360b8`); - `check` → `ok 10 patches apply cleanly in order` and `ok core/ equals ds-bot/ + patches/ds-bot/`; `canonical` → `ok patches/ds-bot/ is what generate writes (generate is a no-op)`; `build-client.mjs --check` → no-op. ## Red first Two independent runs on the base `f6fcb7ffabee43da79388313cbcdb7ada9111dcb`, each in a fresh checkout of the base carrying only the new test file: **30 tests, 28 pass / 2 fail** both times, and the two failures are the two new behaviours: 1. `ThinkFlowLab#2030: five tasks draw three rows and the title says the real count` — the rendered title is `Scheduled tasks` where `Scheduled tasks · 5` is expected (the three `.bt-task-line` rows already pass today). 2. `ThinkFlowLab#2030: the row after the three says how many are left and opens the list, not the new-task form` — `0` buttons with class `bt-more`, expected `1`. 3. Control, green on the base and still green at the tip: three tasks keep the bare title and draw no `more` row; zero tasks keep the empty state, and its click still passes `bot.id`. ## Each new test bites Mutation checks, one at a time, on the fixed tree: | mutation | result | | --- | --- | | title drops the count (`t('Scheduled tasks')`) | case 1 red — 29 pass / 1 fail | | the `more` row passes `bot.id` | case 2 red on the call arguments — 29 pass / 1 fail | | the `more` row drawn at three tasks too (`hidden > -1`) | control red — 29 pass / 1 fail | ## Verified on the head `TZ=UTC ARCH_DOCS_BASE=f6fcb7ffabee43da79388313cbcdb7ada9111dcb npm run verify` in a clean detached checkout of `5c34342690a3a9ad948f084c57515e204f138224`, exit 0: - core `2454/2454`, social `541/541`, tree `127/127`, timeline `135/135`, workflow `336/336`, `.github/scripts` `563/563`, interop `37/37`, live-e2e `24/24` - `arch-docs: OK (9 changed file(s))` against base `f6fcb7ff`; core lint `73 warnings and 0 errors` — the base's own count, with no warning in a line this diff adds - `ok 10 patches apply cleanly in order`, `ok core/ equals ds-bot/ + patches/ds-bot/`, `ok patches/ds-bot/ is what generate writes (generate is a no-op)`; `build-client.mjs --check` no-op Not run: smoke/bench (no host or turn path), and the CI `package` job's pack, typecheck and actionlint. ## The generated patch: 0008 moves text, it does not rewrite it `core/client.js` changes by **+16/−8** (one i18n key, one `ScheduleSummary` hunk), `0001-core-fixes.patch` by **+16/−6** (the `schedules.js` diff), yet `patches/ds-bot/0008-client-bundle.patch` shows **+4,700/−2,925** and grows from 23,095 to 24,870 lines. What that is, measured rather than asserted: - `generate` on a **pristine checkout of the base with no changes at all** reproduces `main`'s `0008` byte-for-byte, so this is not a git-version or tooling difference; - with only this change, **2,618 of the 4,700 added lines are byte-identical to a line the same diff removes**, and 481 lines that are plain context in the base's `0008` are now emitted as removals: the bundle text is re-hunked (303 hunks become 266, one block merging into `@@ -1624,969 +1624,2049 @@`), because `core/client.js` and ds-bot's `client.js` differ almost everywhere and a small edit re-pairs the duplicated minified CSS/JS; - decisive check: applying the whole series with **main's `0008`** substituted reproduces `main`'s `core/client.js` byte-for-byte, and substituting this PR's `0008` changes the result by exactly **+16/−8** — the same delta as `core/client.js`. The patch is a different spelling of the same edit, not a different edit. Nothing in the patch was hand-edited: `canonical` proves the committed series is exactly what `generate` writes, and `check` proves the ten patches still apply in order and reproduce `core/` — the tree that ships. ## Checkpoint <!-- flybot:checkpoint --> ```json { "pr": 2032, "claim": "Dev-3", "issue": 2030, "summary": "The details panel's Scheduled tasks line drew only three rows and said nothing about the rest of a Bot's tasks. Above three it now puts the real count in the title (Scheduled tasks · 5) and draws one more row saying how many are left, which opens the settings list. That row passes no bot id, because an id opens the new-task form rather than the list. Three or fewer tasks, and a Bot with none, render exactly as before, and the empty state keeps its add-one button.", "files": [ "core/src/client/schedules.js", "core/src/client/i18n.js", "core/test/schedules-client.test.js", "core/client.js", "patches/ds-bot/0001-core-fixes.patch", "patches/ds-bot/0008-client-bundle.patch", "patches/ds-bot/files/test/schedules-client.test.js", "docs/subsystems/core-client.md", "changelog.d/2030-schedules-summary-more.changed.md" ], "checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=f6fcb7ffabee43da79388313cbcdb7ada9111dcb npm run verify", "result": "green", "at": "5c34342690a3a9ad948f084c57515e204f138224" }, "next": "none — ready for review" } ``` <!-- /flybot:checkpoint -->
HugoXK
added a commit
to HugoXK/FlyBot
that referenced
this pull request
Nov 1, 2026
…, and the Blocked group is unsorted (ThinkFlowLab#2031) Fixes ThinkFlowLab#2029 Written-by: Dev-2 Owner: Dev-2 Reviewer: Birfy Claim-Id: 0217a22190204dfc8d1a523db2dca665 Branch: dev/2-blocked-card-age Base: f6fcb7f ## What changed The Blocked group answered *why* a card was stuck and nothing else: one line per card, the `inputRequest.question`. The Waiting group beside it answered *how long* and read oldest-first, so a card blocked for three days and one blocked for three minutes drew the same line, and the card stuck longest was not the one on top. 1. `blockedOf(task, now)` (`core/src/host/project-board.js:138-142`) now answers the same age shape `waitingOf` does: `since` = the `inputRequest.requestedAt` stamp the ask itself wrote (`tasks.js`, preserved across a re-ask), `waitedMs` = `now - since`, both `null` when the ask wrote no stamp. It still answers `{ id, reason, since, waitedMs }` and `reason` stays the card's own line; `createdAt`/`updatedAt` are never used as a fallback, because an invented stamp is worse than none. 2. `buildQueues` (`:502-523`, the two sorts at `:521-522`) passes the `now` it already computed and sorts `blocked` with the comparator `waiting.sort` already used, hoisted to `oldestFirst` so the two groups cannot drift. A card with no stamp sorts last. The Waiting group's own output is unchanged — same comparator, same rows. 3. The client (`core/src/client/project-board.js:316`, `:510`) carries `waitedMs` on the blocked row and draws `reason · 2d` when the host measured one, the bare reason when it did not. `waitedLabel` is reused, so a sub-minute span still reads seconds rather than `0m`. Line numbers are this tip (`2b76eb55`), not the base. No write path changed; the board still writes nothing. Base moved while this was in review: `ThinkFlowLab#2028` merged as `f6fcb7ff` and it touches the same two generated files (`core/client.js`, `patches/ds-bot/0008-client-bundle.patch`). The source diff was replayed onto the new base with `git apply --3way` (clean), then the client bundle and the ds-bot mirror were regenerated — the conflict is in the generated files, so they are re-derived, never merged by hand. ## Line by line - `core/src/host/project-board.js` — +17 / −6: `blockedOf` takes `now` and answers the age; `oldestFirst` extracted, `blocked.sort` added. - `core/src/client/project-board.js` — +6 / −2: the blocked row carries `waitedMs`, the note draws the label. - `core/test/project-board.test.js` — +44 / −0: cases 1, 2 and 3. - `core/test/project-board-view.test.js` — +18 / −0: case 4. - `core/client.js` — +2 / −2: generated by `npm run build` in `core/`, never hand-edited. - `patches/ds-bot/0008-client-bundle.patch` +2 / −2 and `patches/ds-bot/files/{src/host,src/client,test}/project-board*`: generated by `node scripts/ds-bot-patches.mjs generate`, never hand-edited. - `docs/subsystems/core-host.md` +1 / −1 and `docs/subsystems/core-client.md` +3 / −1: the read and the row documented. - `changelog.d/2029-blocked-card-age.changed.md` — +1. ## What I ran - Red first, on the unmutated tree at this tip, both files together: 58 tests / 58 pass / 0 fail. Each arm was then re-run twice on this base, two independent runs with the same result: - Mutation A — `blockedOf` keeps `{ id, reason }` (drops `since`/`waitedMs`): acceptance 1 and 3 go red 2/2 (the `ThinkFlowLab#2029` cases "carries the age of its own question" and "no requestedAt has no age"), and acceptance 2 follows because the sort reads the same field; the two files read 58 / 55 / 3 both runs. - Mutation B — delete `blocked.sort(oldestFirst)`: only acceptance 2 goes red, 2/2 (the ordering case: `['task-blocked','task-blocked-old','task-blocked-unstamped']` vs the expected order), 58 / 57 / 1. - Mutation C — the blocked note draws `row.reason` alone: only acceptance 4 goes red, 2/2 (the `.bt-project-reason` text does not end with ` · 2d`), 58 / 57 / 1. - Mutation D — `waiting.sort` reversed: the existing `ThinkFlowLab#1768` waiting assertion goes red, 2/2, so acceptance 5's regression guard still bites, 58 / 57 / 1. - `TZ=UTC ARCH_DOCS_BASE=f6fcb7ffabee43da79388313cbcdb7ada9111dcb npm run verify` at this tip, clean tree: exit 0. `core` 2455 tests / 2455 pass / 0 fail / 0 skipped; `plugins/social` 541, `plugins/tree` 127, `plugins/timeline` 135, `plugins/workflow` 336, `.github` 563 / 563, interop 37 / 37, live-e2e 24 / 24. Skip counts are host-dependent and are not asserted here; the totals above are the run's own output. - `arch-docs: OK (13 changed file(s))`; oxlint `73 warnings, 0 errors` in core (the base's own count); `ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70`; `10 patches apply cleanly in order`; `core/ equals ds-bot/ + patches/ds-bot/`; `patches/ds-bot/ is what generate writes (generate is a no-op)`; `node core/scripts/build-client.mjs --check` exit 0; `node .github/scripts/changelog.mjs check` — green. - Generation on the new base: `node scripts/ds-bot-patches.mjs generate` run twice, the second run byte-identical (every file under `patches/ds-bot/` has the same sha256). `patches/ds-bot/0008-client-bundle.patch` is `sha256 0f965ee5a5466280559eff550c2468488d606c09668a24c5cc2af6cad655c910`; `0002-flybot-api-host.patch` is `sha256 96be0fa26f9e818da4011d8f6a3e2ce4c1f7f3772038041c2b3c926fc326cb4c`. - Not run: the smoke bench (`dev/bench.sh smoke`) — `index.js` is untouched and this is a read-model field plus a sort, so the smoke tier adds nothing here. ## Checkpoint <!-- flybot:checkpoint --> { "pr": 2031, "claim": "Dev-2", "issue": 2029, "summary": "The Blocked group on a Project board now says how long each card has been stuck. `blockedOf` answers `since` / `waitedMs` from the question's own `requestedAt` stamp and from nothing else, so a card whose ask wrote no stamp shows no duration. `buildQueues` sorts the group oldest-first with the comparator Waiting already uses, an undated card last. The row draws `reason · 2d` and keeps the reason alone without a stamp. The Waiting group's own output is unchanged.", "files": [ "core/src/host/project-board.js", "core/src/client/project-board.js", "core/client.js", "core/test/project-board.test.js", "core/test/project-board-view.test.js", "patches/ds-bot/0008-client-bundle.patch", "patches/ds-bot/files/src/host/project-board.js", "patches/ds-bot/files/src/client/project-board.js", "patches/ds-bot/files/test/project-board.test.js", "patches/ds-bot/files/test/project-board-view.test.js", "docs/subsystems/core-host.md", "docs/subsystems/core-client.md" ], "checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=f6fcb7ffabee43da79388313cbcdb7ada9111dcb npm run verify", "result": "green", "at": "2b76eb55698954a65d3290c615eb00808c6287b8" }, "next": "none — ready for review" } <!-- /flybot:checkpoint --> Co-authored-by: HugoXK <hugoxk@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
client: a connector's failure text is one click to copy
Fixes #2027
Owner: Dev-1
Reviewer: Birfy
Written-by: Dev-1
Claim-Id: db3db67d2c824345bf5577b2fd3de232
What changed
core/src/client/connectors.js:235— a row in theerrorstate now drawsCopy errorbesideTry again; the other two actions and every other state are untouched.core/src/client/connectors.js:220-222— the click handsconnector.error(the stored text, not the row'sCould not connect: …wording) tocopiedToClipboard()fromcore/src/shared/remote-label.js:252, and setscopiedorfailedfrom its answer.core/src/client/connectors.js:161-162,192— the label state, plus a reset effect keyed on the connector'sstateorerrorso a second failure starts fromCopy erroragain. The reset is implemented but not asserted:core/test/connectors-page.test.js:30stubsuseEffect, so the suite stays green with the effect deleted. This bullet is a code-reading claim, not a tested one.core/src/client/i18n.js:163-164— ZH forCopy error/Could not copy the error(Copiedalready existed).core/test/connectors-page.test.js— four cases: the copy itself, the two failure paths, the state guard, the ZH strings.core/client.jsandpatches/ds-bot/0008-client-bundle.patcharebuild-client/generateoutput;patches/ds-bot/0001-core-fixes.patchcarries the source diff;patches/ds-bot/files/test/connectors-page.test.jsis the generator's whole-file mirror of the new test.changelog.d/2027-connector-error-copy.fixed.md, and one paragraph indocs/subsystems/core-client.md.The cause: the row's only actions were
Try againandDisconnect, so since #2022 a 2,000-character host failure could only be handed on by selecting it by hand — which a phone makes nearly impossible. The copy reuses the shared helper so a page with no clipboard, or a refused write, reportsCould not copy the errorinstead of a quiet success (#1347).Scope
938a1ecafd24ea593a2dca0ecadd76a0dd7c0f52 → c9b3d90d4c9fe53c2f941aca09aaa2f1878dad94, 1 commit, 9 files+1358/−2282.Verified commit:
c5da60918ccda26decf04b9d8bc0f0826c0ee96c— a local verification commit that was never pushed, so it cannot be looked up on either remote. Its tree isde7ad18717249c9df25f91d5779eefee34a99061; the pushed headc9b3d90d4c9fe53c2f941aca09aaa2f1878dad94was generated from that same tree by the push path, so the two trees are identical.core/src/client/connectors.jscore/test/connectors-page.test.jscore/client.jscore/src/client/i18n.jsdocs/subsystems/core-client.mdchangelog.d/2027-connector-error-copy.fixed.mdpatches/ds-bot/0001-core-fixes.patchpatches/ds-bot/0008-client-bundle.patchgenerateafter the rebase (see below)patches/ds-bot/files/test/connectors-page.test.jsNo
patches/ds-bot/files/src/client/connectors.jswas created: that mirror does not exist on938a1eca, andconnectors.jsships inside the0008bundle.generatere-chunked0008after the rebase: adding lines inside a 901-line hunk made git split it into smaller ones, which is the+977/−2279in that file —checkandcanonicalbelow show the patch set still reproducescore/.Red first on
938a1ecaOnly
core/test/connectors-page.test.jsadded,connectors.js/i18n.jsat base: 4 tests, 0 pass / 4 fail, three runs, three times red.a failed row offers Copy error(false !== true)a failed row offers Copy error with no clipboard tooonly the failed row offers itCopy error has a Chinese entry('undefined' !== 'string')One mutation per assertion, applied alone on the fixed tree:
statusText(connector)) on the button and on the clipboardCopy errorlabel, so that change breaks all three locators; case 1 also fails its clipboard assertionstatusText(connector)instead of the storederror, leaving the label alonethe exact stored text lands on the clipboard,'Could not connect: …'.then(() => setCopyState('copied')))a page with no clipboard says sostate === 'error'guard (draw the button on every row)a ready connector has nothing to copyVerification at the tip
Tip SHA:
c9b3d90d4c9fe53c2f941aca09aaa2f1878dad94TZ=UTC ARCH_DOCS_BASE=938a1eca npm run verify→ exit 0.arch-docs: OK (9 changed file(s))atARCH_DOCS_BASE=938a1ecafd24ea593a2dca0ecadd76a0dd7c0f52.@flybot/social(36 warnings),@flybot/timeline(9),@flybot/tree(2),@flybot/workflow(16), 0 errors.node scripts/ds-bot-patches.mjs check: pin ok, 10 patches apply cleanly,core/ equals ds-bot/ + patches/ds-bot/.node core/scripts/build-client.mjs --checkclean.node scripts/ds-bot-patches.mjs generaterun twice after the rebase: the wholepatches/ds-bot/tree hashes to531c2c727e5d809d2364d0e79a6c73c242aacacf0a77e203c31ecfa9b53607b8before and after, andcanonicalreportsgenerate is a no-op.node .github/scripts/check-mojibake.mjs --check: 0 occurrences of U+FFFD in 1,377 text files.Docs
docs/subsystems/core-client.md(Connectors section): one paragraph under the Feishu row saying a failed row offersCopy errorbesideTry again, that it copies the storederror(up to the 2,000-character host window) through the shared helper, thatCopiedis read only when the write landed andCould not copy the errorotherwise, that it is drawn only in theerrorstate, and namingcore/test/connectors-page.test.js. Thecore-hostbound itself is unchanged — this issue only makes the text reachable.What I did not run
core/dev/smoke.mjs) — noindex.jsorsrc/host/change.packagejob (pack check, typecheck, actionlint) and the merge queue.Checkpoint
{ "pr": 2028, "claim": "Dev-1", "issue": 2027, "summary": "A failed connector row offered only Try again and Disconnect, so the host's own failure text, up to 2,000 characters since #2022, could only be handed on by hand-selecting it. The row now draws Copy error beside Try again and puts the stored connector.error on the clipboard through the shared copiedToClipboard(). The button reads Copied only when the write landed, and Could not copy the error when the page has no clipboard or the write was refused. ready and off rows are unchanged.", "files": [ "core/src/client/connectors.js", "core/src/client/i18n.js", "core/test/connectors-page.test.js", "core/client.js", "patches/ds-bot/0001-core-fixes.patch", "patches/ds-bot/0008-client-bundle.patch", "patches/ds-bot/files/test/connectors-page.test.js", "changelog.d/2027-connector-error-copy.fixed.md", "docs/subsystems/core-client.md" ], "checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=938a1eca npm run verify", "result": "green", "at": "c9b3d90d4c9fe53c2f941aca09aaa2f1878dad94" }, "next": "none — ready for review; the Verdict has to name this tip." }