Skip to content

fix(messages): show "New messages" in channels longer than one page - #3071

Merged
matt2e merged 2 commits into
mainfrom
new-messages-line-doesnt-show
Nov 10, 2026
Merged

matt2e merged 2 commits into
mainfrom
new-messages-line-doesnt-show

Conversation

@matt2e

@matt2e matt2e commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The "New messages" divider from #2083 never appeared in a channel with more than 20 top-level messages, which is most channels anyone is behind in. The channel window loads 20 rows on entry, so such a channel always enters with hasMore, and the entry boundary refused to settle while history was incomplete. The guard existed for a real reason: with unloaded rows above the window, the first unread loaded row need not be the first unread row.

Repro (fixture relay, readState: true, historyCounts: { alpha: 25, beta: 15 }):

  1. Open #Alpha (25 messages), stay at the bottom for a second so the catch-up read saves.
  2. Open #Beta (15 messages) and stay at the bottom.
  3. From another account, post three messages to #Alpha, then reopen it: three new rows, no line.
  4. Post three messages to #Beta, reopen it: the line sits above the first new row.

Same read state, same three arrivals; only the history length differs.

Fix

  • oldestUnreadId on the channel snapshot (src/features/relay/unread.ts): names the oldest unread message the timeline shows (a top-level post or broadcast reply, in rendered order). It ignores own messages, quiet agent rows and non-broadcast replies, follows reads, and clears when nothing is unread. It comes from the same bounded evidence as observedCount. equal() compares it so subscribers see it move.
  • Timeline check (src/features/messages/ChannelTimeline.tsx): when entry history is incomplete, the boundary settles with no line unless oldestUnreadId is a row loaded on entry. Complete-history visits still place the line at the first unread entry row, but no longer wait on hasMore or historyLimited, so a window that gains hasMore after entry (live arrivals trimming it) settles from the entry rows instead of staying pending. The pending boundary filters rows to the entry set before classifying them. The line still stays put for the visit as older pages load, rows arrive and the reader scrolls. MessageManagement.tsx is untouched; entryHistoryComplete means what it meant.
  • sampled wait: a new snapshot flag saying a repair has read every channel in unread scope since evidence was last emptied (cache clear or capacity overflow) or a channel entered that scope. Later repairs (reconnect, backstop, refresh) only add evidence and leave it set, so they publish no extra snapshots. Incomplete-history visits wait for it before settling. Without it, a Chromium probe that held the repair read and re-entered a 25-message channel drew the line at message 5 while messages 0–4 were unread too.
  • Read-row guard: with incomplete history, the line also needs a read entry row above the first unread one. This covers a repair batch that filled its 500-row limit and stopped inside the window, leaving the first loaded row the oldest unread message known. Catch-up's activity: mark reads all ordinary rows up to a timestamp, so a read ordinary row vouches for the rows above it.
  • Cache clear now restarts the repair: clear() used to leave a connected session without a repair until the 15-minute backstop or a reconnect; it now starts one at once, as a newly accessible channel does. An overflow still waits for the next recovery tick.
  • Help and docs: docs/buzz-help.md describes the rule instead of the old limitation; docs/unread.md documents both fields with the bounded-evidence caveat.

Known limits

  • The line can appear a moment after entry, once the repair finishes. Until then the channel holds its own reading (dwell and bottom catch-up), so entering does not erase the evidence its divider waits for; the timeline asks the store for the repair itself, so a host without the shell's startup request still gets one.
  • If that repair fails or evidence overflows (freshness stale, including a stale reconnect), the hold is released and reading resumes with no divider for that visit.
  • Rows dwelt on during the hold and left before it lifts are not marked read by that dwell; the next dwell reads them. Catch-up at the bottom still reads the ordinary rows once the hold lifts.
  • No line when the unread messages begin exactly at the first loaded row (the read-row guard has nothing to vouch for them).
  • Mentions, broadcasts, broadcast replies and messages marked unread above the window are read only by their own msg:/thread: marks, never by activity:. A repair sample that stopped short can still hide one of them, and the line then lands below it. Recorded in docs/unread.md.
  • The flipped pinned test variant depends on mount timing: a remote read that leaves a loaded row the oldest unread places the line there only if it lands before the timeline is presented. The same read after the boundary has settled leaves no line, because the boundary is captured once per visit.

Reviewer notes

Testing

Check Result
tsc --noEmit, Biome on changed files clean
Four related Vitest files (ChannelTimeline.restore, unread, MessageManagement, ChannelTimeline) 479 passed
Relay, messages, channel-navigation, bundled/channels suites 7,706 passed
Full Vitest 1,034 files passed
tests/browser/new-message-boundary.spec.mjs 4/4 Chromium, 4/4 WebKit, 20/20 repeated
Mutation checks removing each guard, the restart or the reset breaks at least one test

The new browser case seeds 25 messages, reads to the bottom, receives three from a peer and asserts the line sits above the first; it fails on main and passes here. New restore cases place the line inside incomplete history (presented and late mount, stable while older rows page in) and omit it at a first loaded row with no read row before it. unread.test.ts and unread-startup.test.ts cover oldestUnreadId and sampled through refresh, clear, overflow and a channel entering scope mid-repair.

Not run: the native app via just desktop, the notification and Inbox DM entry paths, and the restart after a clear against a real relay. The final amend changed only tests and the publish order of one reset, so the browser spec and wider suites were not re-run after it.

Before and after

Same Playwright flow against the fixture relay (readState: true, historyCounts: { alpha: 25, beta: 15 }) on main a905c4865 (left) and this branch (right). Around 8–13 s #Alpha (25 messages) is reopened after three peer messages: the left has nothing above "Later message 1", the right shows the "New messages" line between message 24 and "Later message 1". From about 15 s #Beta (15 messages) shows the line on both sides.

before-after-side-by-side.mp4
Before (main a905c4865) After
Before: #Alpha reopened after three new messages, no line above "Later message 1" After: the "New messages" line sits between message 24 and "Later message 1"

🤖 Generated with Claude Code

@matt2e
matt2e marked this pull request as ready for review November 9, 2026 07:31
@matt2e
matt2e requested a review from a team as a code owner November 9, 2026 07:31

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord’s automated source review via Wes’s account, integrating Rocket, Mantis and Groot.

No actionable findings at head ed6d147de52a68444fe5949d7ce67e3640541f89 against base/merge-base 40e6ccb597cf9d8b47f9692de8285458cc304017.

Reviewed the unread producer and snapshot publication, bounded repair/reset lifecycle, timeline entry ownership and divider placement, and the changed regression-test source. The change reuses the existing repair and preserves the once-per-visit boundary. The documented sample limits remain: this does not establish an exact unread boundary beyond retained evidence.

Source-only review: no tests, builds or apps were run, and no live-relay behavior was exercised. The PR description and both screenshots were inspected; the comparison video was not visually reviewed. The author’s reported test results were not independently reproduced, including the stated lack of browser/wider-suite reruns after the final amend.

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 One remaining limitation in the paged-entry divider fix.

matt2e and others added 2 commits November 10, 2026 03:05
Read a channel with more than 20 top-level messages to the bottom,
switch away, let three messages arrive and come back: the new rows were
there but nothing marked where you stopped. The channel window loads 20
rows on entry, so such a channel always enters with `hasMore`, and the
entry boundary refused to settle on anything when history was
incomplete. Channels under one page showed the line; busy channels never
did.

The guard existed because the first unread loaded row need not be the
first unread row when unloaded history sits above the window. The
timeline now places the line with history above the entry rows only
when the unread store can vouch for it, through two optional
channel-snapshot fields in unread.ts:

- `oldestUnreadId` names the oldest unread message the timeline shows
  (a top-level post or broadcast reply, in rendered order). It ignores
  own messages, quiet agent rows and non-broadcast replies, follows
  reads and clears when nothing is unread. It comes from the same
  bounded evidence as `observedCount`, so missing history can make a
  later message the oldest one known.
- `sampled` says a repair has read every channel in unread scope since
  evidence was last emptied (cache clear or capacity overflow) or a
  channel entered that scope. Later repairs (reconnect, backstop,
  refresh) only add evidence and leave it set, so they publish no extra
  snapshots. It lives on the store's snapshot because the store owns
  the repair; `sync()` reports the read-state module.

`equal()` compares both so subscribers see them change. When entry
history is incomplete, ChannelTimeline waits for `sampled`, then
settles with no line unless `oldestUnreadId` is a row loaded on entry
with a read entry row above it. Entry history is complete only when the
visit captured a ready, verified, idle window with no more history, so
visits that entered a cached, refreshing or still-paging window take
this path too: a short channel entered from cache could not show the
line before and now can under these rules. The line still stays put for
the visit as older pages load, rows arrive and the reader scrolls.

This narrows a race; it does not close it. Every relay read, the
window's own page included, feeds the store and marks its evidence
observed, so the store's oldest known unread message can sit in the
window only because it has seen nothing above it. Two guards cover
different parts of that:

- The `sampled` wait covers the store not having repaired yet since
  startup, a cache clear, an overflow or a channel becoming accessible.
  A Chromium probe that held the repair read and re-entered a 25-message
  channel drew the line at message 5 while messages 0-4 were unread too.
  With the wait in place and the read-row guard disabled, the same probe
  showed no line while the repair was held and none after it returned
  (oldest unread: message 0). A cache clear used to leave a connected
  session without a repair until the 15-minute backstop or a reconnect;
  clear() now starts one at once, as a newly accessible channel does, so
  the line, unread counts and Inbox activity come back promptly. An
  overflow still waits for the next recovery tick. The wait does nothing
  about a repair that finished with a short sample. The line can now
  appear a moment after entry, and a host that never runs the repair
  never places it with history above the window.
- The read-row guard covers a repair batch that filled its 500-row limit
  and stopped inside the window, leaving the first loaded row the oldest
  unread message known, but only for ordinary rows: catch-up's
  `activity:` mark reads all of them up to a timestamp. It never reads
  mentions, broadcasts, broadcast replies or messages marked unread;
  only their own `msg:` or `thread:` marks do. A short sample can still
  hide an unread one of those above the window, and the line then lands
  below it. A row read only by its own mark says nothing about the
  unloaded rows above it either. docs/unread.md records this residual.
  The cost is no line when the unread messages begin exactly at the
  first loaded row.

Complete-history visits still place the line at the first unread entry
row, but no longer wait on `hasMore` or `historyLimited`: a window that
gains `hasMore` after entry (live arrivals trimming it) now settles from
the entry rows rather than staying pending. The pending boundary now
filters rows to the entry set before classifying them, so rows from
older pages are not looked up on every render.

`UnreadSnapshot` is re-exported through the FOUNDATION type entry
`src/plugins/author.ts`. This adds two optional fields in unread.ts and
does not edit author.ts, the same shape as the earlier `row` field.

Help text now describes the rule instead of the old limitation, and
docs/unread.md documents both fields with the bounded-evidence caveat.

Tests: the restore fixture's snapshot derives `oldestUnreadId` from
every row it built, loaded or not, and reports `sampled` unless a test
clears it. New restore cases place the line inside incomplete history
(presented and late mount, stable while older rows page in) and omit it
at a first loaded row with no read row before it. A repair-wait case
pins the guard's limits: with the oldest unread in the window below a
receipt-read row and the repair unfinished there is no line; once the
repair finishes there is none if it found an older unread row, and the
line sits at the loaded row if it did not, which the store cannot tell
from a sample that stopped short. The pinned "outside the entry window"
case keeps two variants; its third flips: a remote read that lands
before the timeline is presented and leaves a loaded row the oldest
unread now places the line there. That depends on mount timing: the
same read after the timeline has settled (the second variant) leaves no
line, because the boundary is captured once per visit.

unread.test.ts covers `oldestUnreadId` and `sampled`: absent with only
window rows and during the first repair, kept with no snapshot change
through a refresh, dropped by a cache clear and restored by the repair
the clear starts, and dropped for a channel entering scope until its
repair finishes; when that happens mid-repair, subscribers see the reset
at once and it lasts until the restarted repair finishes.
unread-startup.test.ts checks an overflow clears it. The clear cases of
three startup tests pinned that nothing is read after a clear; they now
expect the restart and still check that the abandoned repair dispatches
and publishes nothing more. An Inbox test that expected activity to stay
hidden after a clear now checks it is not reported as nothing to read
before the repair returns, and that the repair brings it back. The
idle-feed Refresh test passed for the wrong reason once the restart
returned the mention before Refresh; it now shows that mention returning
on its own while the cleared feed stays idle and offers Refresh instead
of a spinner, and a feed-only reaction row returning only after Refresh.
A browser case seeds 25 messages, reads to the bottom, receives three
from a peer and asserts the line sits above the first; it fails on main
and passes here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: mtoohey <mtoohey@squareup.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
…its for the repair

The entry boundary waits for the unread store's `sampled` flag when entry
history is incomplete. Meanwhile the reader is already at the bottom, so
after the 300 ms dwell catch-up read every loaded row and observe read
the unread ones. Either advanced the read revision, and the boundary's
revision check then settled the visit with no line before the repair
could place one. The reads also erased the oldest-unread evidence the
boundary waits for, so removing the revision check alone would not have
helped: the boundary has to settle first and reading follow. This is the
normal startup state, where Buzz restores a cached channel while the
startup repair is still running, so the divider was lost on nearly every
launch.

The `sampled` branch of the boundary candidate now reports
`awaitingRepair`, and the timeline passes `enabled: marksRead &&
!awaitingRepair` to `useReading`. That branch is reached only after every
earlier gate passed, so the hold applies exactly when the repair is the
sole blocker; re-enabling runs the hook's effect, which schedules dwell
at once. A failed repair or an evidence overflow marks the snapshot
stale, which an earlier gate catches, so the hold ends with the repair
either way; a read-intent change from elsewhere still settles the visit
with no line and releases reading. The store only repairs once asked, so
the timeline calls `ensure()` while holding: in the desktop shell the
sidebar, Inbox and Channels pages already asked and a finished repair
answers at once, and a host that mounts a timeline alone no longer holds
reading forever.

Tradeoff: rows dwelt on during the hold and left before it lifts are not
marked read by that dwell; the next dwell reads them.

Tests: the restore fixture gains an opt-in `reading` knob (a frontier-sync
read handle whose catch-up and observe mocks clear the unread rows,
advance the revision and publish, as the store does), a stubbed `ensure`,
`setFreshness`, and a focused-reader dwell recipe. Three new cases enter
a channel with history above the window while the repair is unfinished:
the hold leaves catch-up and observe uncalled and the line absent, then
places the line and catches up once `sampled` is set; a stale repair
releases reading with no line; a revision change from elsewhere settles
with no line and releases reading. All three fail on the previous commit,
where catch-up fires at 300 ms and the line never appears.

Docs: docs/unread.md records the hold and when it ends; the help guide
says the divider is settled before the channel marks anything read.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@matt2e
matt2e force-pushed the new-messages-line-doesnt-show branch from ed6d147 to b7934cb Compare November 9, 2026 16:13

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Star Lord automated source review via Wes’s account, integrating Rocket’s lifecycle, Mantis’s correctness and Groot’s product/test review.

Reviewed the own-reading hold follow-up at b7934cb80ac5d4fcc227f219988092fd5b2009d9 against base/merge-base 5296c33d0ac5b49c99c2bfc8560a7fc4a878d216. The original feature patch is unchanged from the earlier reviewed revision. One P2 finding is attached inline.

Optional documentation clarification: docs/buzz-help.md:328–330 promises that the divider settles before the channel marks anything read, but only the unread-repair wait holds reading. Scope that sentence to the repair wait, as docs/unread.md does.

Source-only: no tests, builds or apps run, and no runtime reproduction. Reviewed the changed test assertions and call paths; author-reported test results were not independently verified. The current PR description was inspected; the two unchanged screenshots retain the earlier inspection, while the comparison video was not visually inspected. The documented bounded-history and once-per-visit limitations remain. This is a COMMENT review, not approval or merge authorization.

@kazemek21 kazemek21 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kyle reviewed this PR and the Nexus review-tracker notes. Approved with the non-blocking suggestion below.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

@matheuskiser

Copy link
Copy Markdown
Contributor

anyway to make the new messages divider a different color? I think this will be hard to identify in light or dark theme

@matt2e

matt2e commented Nov 10, 2026

Copy link
Copy Markdown
Contributor Author

anyway to make the new messages divider a different color? I think this will be hard to identify in light or dark theme

I agree but trying to minimise changes before launch and design team is avoiding last minute changes. It'd be good to raise it in the design feedback channel @matheuskiser

@matt2e
matt2e merged commit c4b8ecd into main Nov 10, 2026
66 checks passed
@matt2e
matt2e deleted the new-messages-line-doesnt-show branch November 10, 2026 01:49
gilgtm added a commit that referenced this pull request Nov 10, 2026
…nt-progress-rows

* origin/main: (53 commits)
  fix(search): make Command F search the Bestie conversation (#3084)
  fix(plugins): keep failed update checks on the plugin's Settings row instead of a toast (#3088)
  Show single-agent Stop on the activity bubble (#3140)
  fix: support explicit hosted browser origins (#3149)
  fix: keep Codex tool namespace descriptions bounded (#3128)
  fix(messages): show "New messages" in channels longer than one page (#3071)
  Keep shared agent guidance generic (#3134)
  fix(typing): a thread reply also ends the agent's channel-level typing (#3148)
  Stream protected evidence across agent runtimes (#2882)
  feat(identity): consolidate key and account controls (#3069)
  Add portrait-colored foil and animated agent card reveal (#3144)
  NEXUS-4190: Disclose unavailable saved Solo choices (#2544)
  docs: mark the plugin API as preview and say which parts are steady (#3129)
  Open the app with a saved key; check the account's key in the background (#3064)
  fix(federated-identity): name only communities that require NIP-FI as affected by a switch (#3072)
  feat(pairing): signed in with Builderlab, say to get the app instead of pairing (#3039)
  Show Add agent button on shared agent previews (#3131)
  NEXUS-4224: Preserve peer unread intent during automatic reads (#2641)
  fix(plugins): review a pinned link to an installed plugin's source as an update (#3024)
  perf(profiles): resume verified-profile batches after a planned renewal (#2293)
  ...

Signed-off-by: Gil Birman <gil@squareup.com>

# Conflicts:
#	docs/buzz-help.md
matt2e added a commit that referenced this pull request Nov 10, 2026
…3169)

Follow-up to #3071, which made a channel longer than one page wait for
the unread store's repair before placing the "New messages" line and
held the channel's own reading meanwhile. This addresses the three
review threads left open when that PR merged.

## Problem

1. **A failed or overflowed repair held reading forever** (Kalvin, Wes).
The hold keyed on `sampled` alone. A repair that failed, overflowed its
evidence budget, or was abandoned by a dropped connection or an access
change left `sampled` unset with `requested` already true, so `ensure()`
started nothing. Once later live evidence made the store observed again,
the timeline held reading until some unrelated repair happened to
succeed.
2. **Any read row counted as the witness above the line** (kazemek21). A
row read only by its own `msg:` receipt (dwell short of the bottom) says
nothing about the rows above it, so a short repair sample could place
the line below a row whose predecessors were still unread.
3. **buzz-help overstated the hold** (Wes's review summary). It said the
divider settles before the channel marks anything read, as if that
applied to every visit.

## Fix

1. Channel snapshots now report `sampling` while a repair that can set
`sampled` is reading, about to restart, or not yet requested. The
timeline holds reading only while it is set; otherwise the visit settles
with no line and reading resumes, including when the channel is entered
after such a repair already ended. `sampling` stays set across the gap
between a repair settling and its restart (`repairAgain`), so the hold
does not flicker.
2. `attention()` reports `prefixRead` when a channel-wide read reaches
the row: the channel's read frontier, the `activity:` catch-up mark for
an ordinary top-level message, or the retention floor. Only such a row
lets the line sit below it.
3. `docs/buzz-help.md` scopes the wait to the history check and says
reading resumes if that check fails; it also says the line is absent
when the rows above were only read one by one. `docs/unread.md` and the
code comments record both behaviours and the remaining residual.

## Testing

Run with Hermit via `bin/pnpm`, on b76ba40 (parent 5cf1713, current
`main`).

| Scope | Result |
|---|---|
| `ChannelTimeline.restore.test.tsx`, `unread.test.ts`,
`unread-startup.test.ts`, `use-reading.test.ts`,
`use-reading.dm.test.ts` | 5 files, 422 passed |
| `vitest related` on `unread.ts` and `ChannelTimeline.tsx` | 240 files,
7,143 passed (run on the parent c4b8ecd before the fast-forward to
5cf1713; that commit touches only Codex/MCP files) |
| `tsc --noEmit`, Biome, `git diff --check` | clean |
| Chromium `tests/browser/new-message-boundary.spec.mjs` | 4 passed,
including the 25-message channel |
| Not run | WebKit journey, the full suite, the native app, manual
testing |

Each new regression test fails without its fix:

- Reverting the timeline's `sampling` release fails 3 tests: the
failed-repair case where the store becomes observed again before any
dwell, and entry after an already-failed and an already-overflowed
repair.
- Reverting the `prefixRead` check fails the receipt-only witness case.
- Dropping the `repairAgain` term fails the store test's restart-gap
assertion.
- Removing `sampling` and `prefixRead` from the store fails the 3 store
tests, including the startup test that walks failure, retry and overflow
against a live session.

## Known limits

- A channel read part-way by scrolling (rows read only by their own
`msg:` marks) now shows no line on return. The line needs a row above it
that a channel-wide read reached.
- Unread mentions, broadcasts, broadcast replies and marked-unread
messages above the loaded window remain the documented residual:
catch-up's `activity:` mark never reads them, so a short sample can
still hide one.
- In the startup test harness, the first queried roster revokes access
and abandons an in-flight repair without restarting it. That store
behaviour predates this change; `sampling` now reports it correctly.
Possible follow-up, not changed here.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
damienrj added a commit that referenced this pull request Nov 10, 2026
…rminal-font-picker

* 'main' of https://github.com/block/buzz-app: (168 commits)
  Unify Membership sharing and live roster updates (#3147)
  fix(activity): show one working cue per agent request (#3166)
  Introduce opt-in first-agent setup (#2804)
  fix(codex): drop the security-review sign-in prompt from harness defaults (#3188)
  feat(releases): publish desktop download manifest (#3154)
  fix(mcp): preserve current attachments across edits (#2937)
  fix(agents): call the Claude and Codex first-run download "the integration" (#3165)
  chore(goose): bump bundled goose-acp to v1.58.1 (#3161)
  Revert "ci: use larger GitHub runners for Rust and browser journeys (#3090)" (#3180)
  Show Goose's unset thinking level as Default, not Off (#3124)
  ci(release): publish stable and beta desktop releases from v tags (#3150)
  fix(messages): end the New messages hold when no repair can sample (#3169)
  fix(search): make Command F search the Bestie conversation (#3084)
  fix(plugins): keep failed update checks on the plugin's Settings row instead of a toast (#3088)
  Show single-agent Stop on the activity bubble (#3140)
  fix: support explicit hosted browser origins (#3149)
  fix: keep Codex tool namespace descriptions bounded (#3128)
  fix(messages): show "New messages" in channels longer than one page (#3071)
  Keep shared agent guidance generic (#3134)
  fix(typing): a thread reply also ends the agent's channel-level typing (#3148)
  ...

Signed-off-by: damienrj <damien@squareup.com>

# Conflicts:
#	src-tauri/Cargo.toml
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.

5 participants