Skip to content

fix(client): write only the visible turn tail when leaving a chat - #3096

Merged
Birfy merged 1 commit into
mainfrom
dev/3079-visible-visit-tail
Nov 15, 2026
Merged

Birfy merged 1 commit into
mainfrom
dev/3079-visible-visit-tail

Conversation

@Birfy

@Birfy Birfy commented Nov 15, 2026 •

Copy link
Copy Markdown
Collaborator

Written-by: Dev-1
Fixes #3079
Owner: Dev
Reviewer: Reviewer
Tip SHA: 30c38974acedf0278bd94a42341875296ce02c57
Base: 3e901fab3326883f801db0d53e44f8998a4d1a1b — live main, read from GET /repos/ThinkFlowLab/FlyBot/git/ref/heads/main at 2026-11-15T01:03Z before the tree was cut; no rebase.
Docs-not-needed: no — docs/subsystems/core-client.md updated (a leave now writes only the reported visible tail), and changelog.d/3079-leave-visible-tail.fixed.md is the user-visible note; no other subsystem doc changes.
Bot: FlyBot Dev-1
Tier: T1 — core/src/client/{index,header,cells}.js plus the generated core/client.js and patches/ds-bot/0008; no host, plugin, workflow or policy/gates.json file is touched.
Tree: 54c94028d1950e2c5bb7479a15f25c706eb1c93b

Ran: red-first twice on 3e901fab with only the new test added (0 pass / 7 fail, exit 1, identical both runs); at the tip the same command 7/7 twice; TZ=UTC npm run verify exit 0 (nine suites, 0 failures); node scripts/ds-bot-patches.mjs check and canonical; node core/scripts/build-client.mjs --check exit 0; node .github/scripts/changelog.mjs check; oxlint 0 errors.
Not run: dev/bench.sh smoke — it needs the DSH desktop bench at ../deepseek-harness and a mock model, and this diff is the browser side only (no host index.js).

What was wrong

leaveVisits (core/src/client/index.js:125-128) wrote seenAt[seenKey(id)] = Date.now() for every visited Session, on a close (endVisit, :590-591) and on pagehide (:131). The divider side already refuses to settle a visit whose divider was never 'in-view' (header.js:182), so one leave still marked the whole Session read up to the clock: a turn that arrived below the fold, or after the user had scrolled up, was recorded as seen without ever being on screen, and its NEW divider could not come back. That is the inverse of what #441 and #275 fixed.

What changed

  • core/src/client/cells.js — leftSeen(seen, tail, now) beside movedOnSince/settledVisit: the newest turn the view reported, else now, never backwards.
  • core/src/client/index.js — a per-Session visitTails; actions.noteVisitTail; leaveVisits writes leftSeen(max(stored, settled visit's seen), tail) and drops the tail. endVisit and pagehide keep going through it, and the first-open baseline (:224) still falls back to now for a Session that never reported a tail.
  • core/src/client/header.js — useTranscriptView measures the newest [data-chat-turn] row inside the viewport (its end, or first while the turn still runs); NewMessagePills reports it through actions.noteVisitTail(sessionId, tailAt). header-effects.js is untouched.
  • core/client.js and patches/ds-bot/0008 regenerated; core/test/visit-tail.test.js is new and mirrored byte-for-byte in patches/ds-bot/files/test/; docs/subsystems/core-client.md says what a leave writes now; changelog.d/3079-leave-visible-tail.fixed.md is the user-visible note.

Red first

core/test/visit-tail.test.js on live 3e901fab, twice, identical:

$ node --import ./core/test/harness-resolve.mjs --test core/test/visit-tail.test.js
# tests 7
# pass 0
# fail 7        (exit 1)

The three new cases: seen t0 with a turn at t1 > t0 whose divider was never 'in-view' keeps t0 after endVisit (today Date.now()); a settled visible turn advances to at least t1; a Session that never reported a visible tail still advances to now. core/test/turn-tail.test.js:172/174 are untouched and still pass (12/12 with client-bundle and client-apply-order).

Verification at the tip

30c38974acedf0278bd94a42341875296ce02c57 (tree 54c94028d1950e2c5bb7479a15f25c706eb1c93b), 11 files: cells.js +7, index.js +21/−2, header.js +18, core/client.js +31/−1, core/test/visit-tail.test.js new 92 lines, patches/ds-bot/0003 +40/−3, 0008 +10201/−10158 (generated — canonical proves generate is a no-op), 0012 +34/−1, the mirrored test, the subsystem-doc sentence, the fragment.

  • the new file 7/7 twice at the tip
  • TZ=UTC npm run verify: exit 0 — core 3255 tests (3254 pass, 1 skipped, 0 fail), social 684, tree 149, timeline 172, workflow 535, project 147, .github/policy 681 + 37, interop 34, live-e2e 0 failures; oxlint 0 errors in core and every plugin
  • ds-bot-patches check: pin ok, 10 patches apply cleanly, core/ equals ds-bot/ + patches; canonical: no drift
  • build-client.mjs --check: exit 0
  • ARCH_DOCS_BASE=3e901fab node core/scripts/check-arch-docs.mjs: OK (11 changed file(s))
  • changelog.mjs check: 2 well-formed fragments

Checkpoint

{"pr":3096,"claim":"Dev","issue":3079,"summary":"`leaveVisits` wrote `Date.now()` for every visited Session, so turns that never came on screen were stored as seen and the NEW divider never returned. `leftSeen` now keeps the mark at the newest turn `useTranscriptView` reported on screen, falls back to `now` only when a Session never reported one, and never moves backwards. `endVisit` and `pagehide` both write through it, and a settled visit keeps what its divider saw. Red on `3e901fab`: 0/7; at the tip 7/7 and `TZ=UTC npm run verify` exit 0.","files":["core/src/client/cells.js","core/src/client/index.js","core/src/client/header.js","core/client.js","core/test/visit-tail.test.js","patches/ds-bot/0008-client-bundle.patch","patches/ds-bot/files/test/visit-tail.test.js","docs/subsystems/core-client.md","changelog.d/3079-leave-visible-tail.fixed.md"],"checks":{"ran":"base 3e901fab + new test: 0/7; tip: 7/7 twice; TZ=UTC npm run verify exit 0; build-client --check; ds-bot-patches check + canonical; arch-docs OK (11 files); changelog check","result":"green","at":"30c38974acedf0278bd94a42341875296ce02c57"},"next":"Opened for review. T1 needs green `tests-ok` at this tip and no PASS; nothing is stacked on this branch."}

`leaveVisits` stored `Date.now()` for every visited Session, so a turn that
arrived below the fold, or after the user scrolled up, was recorded as seen and
its NEW divider never came back. `useTranscriptView` now reports the newest turn
whose row is inside the viewport through `actions.noteVisitTail`, and a leave (a
close or `pagehide`) stores only that mark (`leftSeen`, never backwards), keeping
the old fallback to `now` for a Session that never reported a visible tail.

Fixes #3079
@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 15, 2026
@Birfy Birfy self-assigned this Nov 15, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 15, 2026
Merged via the queue into main with commit 6d4cf52 Nov 15, 2026
19 of 21 checks passed
@Birfy
Birfy deleted the dev/3079-visible-visit-tail branch November 15, 2026 01:36
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 15, 2026
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Nov 15, 2026
…hinkFlowLab#3097)

fix(social): say every face-to-face chip in the interface language

Fixes ThinkFlowLab#3090
Owner: Dev-5
Written-by: Dev-5
Bot: FlyBot Dev-5
Claim-Id: 1d47d64c35824e5b933240fb5e4f2be3
Tier: T2

`faceChipLabel` sent only `Open` through the translator. `Closed`,
`Left`, `Joining` and the expired matching window were bare literals, so
a zh window kept four of the chip's five states in English, and an
expired window printed the internal status id `ended` instead of a word.
The dictionary carried `Open` only, so routing alone would have stayed
English.

All five states now go through `tr()` with the English word as the key,
the expired state's word is `Ended`, and `core/src/client/i18n.js` gains
the four zh entries (`已关闭` / `已离开` / `已结束` / `加入中`). The new test
installs a zh translator itself, so the shared `social.test.js` keeps
its English default and only its one `ended` assertion moves to `Ended`.
The `core-client` page gains the dictionary line arch-docs asks for when
`core/src/client/i18n.js` or the bundle changes. A dictionary without a
key still falls back to the English word — covered in the closed case,
not by a guessed translation.

The bundle: `core/client.js` is rebuilt from `core/src/client` by
`core`'s own build script, and `node scripts/ds-bot-patches.mjs
generate` regenerates `patches/ds-bot/0001` and `0008` so `core/` stays
equal to `ds-bot/` + patches. The `0008` hunk only adds these four zh
entries; it carries no content of `ThinkFlowLab#2520`, and whichever bundle PR
merges second re-emits the bundle from the new `main`.

Retiring three pins: `core/test/i18n-missing-entries.baseline.json` may
only shrink, and routing these words through `t()` with their zh entries
is exactly what retires them — the scan no longer counts `Closed`,
`Left` or `Joining` as copy that cannot be translated, so their three
stale pins are deleted. The `core-client` page also carries the
dictionary line arch-docs asks for when `core/src/client/i18n.js` or the
bundle changes.

Red first at `cc59a97cbe72e2b118f3d4d91a4b3c04ffe762e5` with only
`plugins/social/test/face-chip-i18n.test.js` added: 5 tests, 1 pass / 4
fail, twice.

## Ran at 6afd9d9
- `node --test plugins/social/test/face-chip-i18n.test.js` → 5/5, twice.
- `npm --prefix plugins/social test` → 692/692 (the base carries
`ThinkFlowLab#3094`'s three cases now).
- `node --test core/test/i18n.test.js
core/test/i18n-missing-entries.test.js core/test/i18n-plugins.test.js` →
25/25.
- `node scripts/lint-plugins.mjs` → 0 errors.
- `TZ=UTC npm run verify` → exit 0 (build-client --check and
ds-bot-patches check included).

## Re-cut onto `cc59a97cbe72e2b118f3d4d91a4b3c04ffe762e5`

Two merge-events moved `main` while this branch waited. `ThinkFlowLab#3096` merged
first and re-emitted `core/client.js` with `patches/ds-bot/0008`
wholesale (`+10201/−10158`), so the old bundle hunk could not apply as
it stood; `ThinkFlowLab#3094` then merged and `main` became
`cc59a97cbe72e2b118f3d4d91a4b3c04ffe762e5`. This branch is cut from that
base; its pushed head is one commit carrying the source change and the
rebuild together, the same twelve-file diff. On this base `node
scripts/ds-bot-patches.mjs generate` rewrote only
`patches/ds-bot/0001-core-fixes.patch` (+10/−1) and
`patches/ds-bot/0008-client-bundle.patch` (+10/−1): the four zh entries
and the rebuilt hunk, carrying no content of `ThinkFlowLab#2520` or `ThinkFlowLab#3096`.
`build-client --check` and `ds-bot-patches check` are both clean, and
`generate` is a no-op on a second run. The previous tip
`4069eb53ac32dfdeb6f4fdc48ebc2a08c6a0c000` and the readings taken on it
are superseded.

## Not run
- Smoke test: no `core/index.js` or `core/src/host/` change.
- `check-pack`: not part of `npm run verify`; CI's `package` job runs
it.

## Checkpoint
<!-- flybot:checkpoint -->
{
  "pr": 3097,
  "claim": "Dev-5",
  "issue": 3090,
"summary": "All five face-to-face chip states now go through the
dictionary: Open/Closed/Left/Ended/Joining, with Ended replacing the
internal `ended` id the expired branch printed. The zh map gains 已关闭 /
已离开 / 已结束 / 加入中, the new test installs its own zh translator so the
shared suite keeps the English default, and only its one `ended`
assertion moved. The bundle and the ds-bot patch series were regenerated
from source, and the i18n-missing-entries pins for the three words
retire because the scan can translate them now.",
  "files": [
    "plugins/social/client.js",
    "plugins/social/test/face-chip-i18n.test.js",
    "plugins/social/test/social.test.js",
    "core/src/client/i18n.js",
    "core/client.js",
    "core/test/i18n-missing-entries.baseline.json",
    "patches/ds-bot/files/test/i18n-missing-entries.baseline.json",
    "patches/ds-bot/0001-core-fixes.patch",
    "patches/ds-bot/0008-client-bundle.patch",
    "docs/subsystems/social.md",
    "docs/subsystems/core-client.md",
    "changelog.d/3090-face-chip-i18n.changed.md"
  ],
  "checks": {
    "ran": "TZ=UTC npm run verify",
    "result": "green",
    "at": "6afd9d9961443a090046dcd14ce4e504a6dc03f5"
  },
  "next": "none — ready for review"
}
<!-- /flybot:checkpoint -->
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.

client: leaving a chat marks turns that were never on screen as seen, and the NEW divider never returns

1 participant