docs: Phase 178 v0.8.143 roadmap, parity, and feature specs
Mark Phase 178 complete and plan Phase 179. Document activedescendant chips, voice category labels, status describedby-only pattern, and triple bucket minPos across SEARCH, CHANNEL_CATEGORIES, NOTIFICATIONS, and IPC. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Pearcord architecture
|
# Pearcord architecture
|
||||||
|
|
||||||
**Current release:** v0.8.142 (desktop app `apps/pearcord`). Phase history and checklists live in [PLATFORM_ROADMAP.md](./PLATFORM_ROADMAP.md). Gap vs a centralized chat product is tracked in [PEARCORD_PARITY.md](./PEARCORD_PARITY.md).
|
**Current release:** v0.8.143 (desktop app `apps/pearcord`). Phase history and checklists live in [PLATFORM_ROADMAP.md](./PLATFORM_ROADMAP.md). Gap vs a centralized chat product is tracked in [PEARCORD_PARITY.md](./PEARCORD_PARITY.md).
|
||||||
|
|
||||||
Pearcord is a **100% peer-to-peer** community chat client on [Pear](https://pear.holepunch.to) / Bare. There is no Pearcord-operated message server: guilds, DMs, discovery listings, and bot events replicate over **Hyperswarm** topics with **protomux** framing.
|
Pearcord is a **100% peer-to-peer** community chat client on [Pear](https://pear.holepunch.to) / Bare. There is no Pearcord-operated message server: guilds, DMs, discovery listings, and bot events replicate over **Hyperswarm** topics with **protomux** framing.
|
||||||
|
|
||||||
|
|||||||
@@ -434,7 +434,11 @@ Pearcord validates itself without manual QA.
|
|||||||
| `npm run test:channel-category-label-count` | Label channel count (`CHANNEL_CATEGORY_LABEL_COUNT_SMOKE_OK`) |
|
| `npm run test:channel-category-label-count` | Label channel count (`CHANNEL_CATEGORY_LABEL_COUNT_SMOKE_OK`) |
|
||||||
| `npm run test:notif-mark-all-disabled-describedby` | Disabled describedby (`NOTIF_MARK_ALL_DISABLED_DESCRIBEDBY_SMOKE_OK`) |
|
| `npm run test:notif-mark-all-disabled-describedby` | Disabled describedby (`NOTIF_MARK_ALL_DISABLED_DESCRIBEDBY_SMOKE_OK`) |
|
||||||
| `npm run test:guild-bucket-minpos-order-pick` | Equal minPos order (`GUILD_BUCKET_MINPOS_ORDER_PICK_SMOKE_OK`) |
|
| `npm run test:guild-bucket-minpos-order-pick` | Equal minPos order (`GUILD_BUCKET_MINPOS_ORDER_PICK_SMOKE_OK`) |
|
||||||
| `npm run test:boot` | `pear run` pre-run (`BOOT_OK`) then Phase 124–177 smokes + `test:companion-boot` |
|
| `npm run test:search-recent-chip-activedescendant` | Chip activedescendant (`SEARCH_RECENT_CHIP_ACTIVEDESCENDANT_SMOKE_OK`) |
|
||||||
|
| `npm run test:channel-category-voice-label` | Voice category label (`CHANNEL_CATEGORY_VOICE_LABEL_SMOKE_OK`) |
|
||||||
|
| `npm run test:notif-mark-all-status-describedby-only` | Status describedby only (`NOTIF_MARK_ALL_STATUS_DESCRIBEDBY_ONLY_SMOKE_OK`) |
|
||||||
|
| `npm run test:guild-bucket-minpos-triple-pick` | Triple bucket minPos (`GUILD_BUCKET_MINPOS_TRIPLE_PICK_SMOKE_OK`) |
|
||||||
|
| `npm run test:boot` | `pear run` pre-run (`BOOT_OK`) then Phase 124–178 smokes + `test:companion-boot` |
|
||||||
| `npm run test:server-folder-prefs-mesh` | Live folder prefs mesh (optional; run manually — can exceed 150s on DHT) |
|
| `npm run test:server-folder-prefs-mesh` | Live folder prefs mesh (optional; run manually — can exceed 150s on DHT) |
|
||||||
| `npm run test:profile` | Profile update + persist (`PROFILE_SMOKE_OK`) |
|
| `npm run test:profile` | Profile update + persist (`PROFILE_SMOKE_OK`) |
|
||||||
| `npm run test:e2e` | Full journey host+guest (`E2E_SMOKE_OK`) |
|
| `npm run test:e2e` | Full journey host+guest (`E2E_SMOKE_OK`) |
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ Gossip: existing `RPC.CHANNEL_CREATE` carries full channel row including `parent
|
|||||||
|
|
||||||
## UI
|
## UI
|
||||||
|
|
||||||
|
- Voice-only categories use “voice channel(s)” in `aria-label`; mixed categories include a voice count (v0.8.143). Smoke: `npm run test:channel-category-voice-label`.
|
||||||
- Category `aria-label` includes channel count when there is no unread or mention activity (v0.8.142). Smoke: `npm run test:channel-category-label-count`.
|
- Category `aria-label` includes channel count when there is no unread or mention activity (v0.8.142). Smoke: `npm run test:channel-category-label-count`.
|
||||||
- Expanded categories omit the `collapsed` suffix in `aria-label`; only collapsed headers include it (v0.8.141). Smoke: `npm run test:channel-category-expanded-label`.
|
- Expanded categories omit the `collapsed` suffix in `aria-label`; only collapsed headers include it (v0.8.141). Smoke: `npm run test:channel-category-expanded-label`.
|
||||||
- Collapsed categories include `collapsed` plus unread/mention totals in `aria-label` (v0.8.140). Smoke: `npm run test:channel-category-collapsed-label-unread`.
|
- Collapsed categories include `collapsed` plus unread/mention totals in `aria-label` (v0.8.140). Smoke: `npm run test:channel-category-collapsed-label-unread`.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Pearcord documentation index
|
# Pearcord documentation index
|
||||||
|
|
||||||
All project documentation lives in **`pearcord-docs`** (`/Users/raven/dev/pearcord/docs/`). **Release:** v0.8.142.
|
All project documentation lives in **`pearcord-docs`** (`/Users/raven/dev/pearcord/docs/`). **Release:** v0.8.143.
|
||||||
|
|
||||||
| Start here | Document |
|
| Start here | Document |
|
||||||
|------------|----------|
|
|------------|----------|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ JSON lines over `pear-pipe` between `ui/app.js` and `apps/pearcord/index.js`. Ha
|
|||||||
|
|
||||||
### Channel pick order (`select-guild` / `loadGuild`)
|
### Channel pick order (`select-guild` / `loadGuild`)
|
||||||
|
|
||||||
When a guild opens, `pearcord-platform` `_selectGuildChannelWithUnread` chooses the active text/forum/thread/announcement channel in this order (v0.8.142):
|
When a guild opens, `pearcord-platform` `_selectGuildChannelWithUnread` chooses the active text/forum/thread/announcement channel in this order (v0.8.143):
|
||||||
|
|
||||||
1. **Unread + mentions** — score `mentions × 1000 + unread` per channel. Pool selection:
|
1. **Unread + mentions** — score `mentions × 1000 + unread` per channel. Pool selection:
|
||||||
- If every unread channel shares one category (`parentId`), only channels in that category compete (mention-weighted score, then lower `position` on ties). Smokes: `test:guild-single-category-pool`, `test:guild-mention-single-category-pick`, `test:guild-dual-category-isolation-pick`.
|
- If every unread channel shares one category (`parentId`), only channels in that category compete (mention-weighted score, then lower `position` on ties). Smokes: `test:guild-single-category-pool`, `test:guild-mention-single-category-pick`, `test:guild-dual-category-isolation-pick`.
|
||||||
@@ -39,7 +39,7 @@ When a guild opens, `pearcord-platform` `_selectGuildChannelWithUnread` chooses
|
|||||||
2. **`lastChannelByGuild`** — USER_PREFS last viewed channel for this guild when valid and viewable.
|
2. **`lastChannelByGuild`** — USER_PREFS last viewed channel for this guild when valid and viewable.
|
||||||
3. **First text channel** — lowest-position `text` channel, else first candidate.
|
3. **First text channel** — lowest-position `text` channel, else first candidate.
|
||||||
|
|
||||||
Smokes: `test:guild-channel-pick-order` (IPC + platform source), `test:guild-unread-position-tie`, `test:guild-in-bucket-position-tie`, `test:guild-mention-in-bucket-pick`, `test:guild-mention-single-category-pick`, `test:guild-dual-category-isolation-pick`, `test:guild-mixed-bucket-pick`, `test:guild-mention-root-bucket-pick`, `test:guild-root-mention-bucket-pick`, `test:guild-bucket-minpos-root-pick`, `test:guild-bucket-minpos-order-pick`, `test:guild-bucket-total-over-minpos`, `test:guild-uncategorized-bucket-pick`, `test:guild-single-category-pool`, `test:guild-category-unread-pick`, `test:guild-category-bucket-pick`, `test:guild-mention-bucket-pick`, `test:guild-bucket-minpos-tie`, `test:guild-mention-channel-pick`, `test:guild-unread-over-last-channel`, `test:guild-last-channel-only-load`, `test:guild-no-unread-last-channel`, `test:guild-first-text-fallback`.
|
Smokes: `test:guild-channel-pick-order` (IPC + platform source), `test:guild-unread-position-tie`, `test:guild-in-bucket-position-tie`, `test:guild-mention-in-bucket-pick`, `test:guild-mention-single-category-pick`, `test:guild-dual-category-isolation-pick`, `test:guild-mixed-bucket-pick`, `test:guild-mention-root-bucket-pick`, `test:guild-root-mention-bucket-pick`, `test:guild-bucket-minpos-root-pick`, `test:guild-bucket-minpos-order-pick`, `test:guild-bucket-minpos-triple-pick`, `test:guild-bucket-total-over-minpos`, `test:guild-uncategorized-bucket-pick`, `test:guild-single-category-pool`, `test:guild-category-unread-pick`, `test:guild-category-bucket-pick`, `test:guild-mention-bucket-pick`, `test:guild-bucket-minpos-tie`, `test:guild-mention-channel-pick`, `test:guild-unread-over-last-channel`, `test:guild-last-channel-only-load`, `test:guild-no-unread-last-channel`, `test:guild-first-text-fallback`.
|
||||||
|
|
||||||
| `select-channel` | `channelId` | Set active text channel (voice IDs join voice) |
|
| `select-channel` | `channelId` | Set active text channel (voice IDs join voice) |
|
||||||
| `join-voice` | `channelId` | Join voice channel + gossip |
|
| `join-voice` | `channelId` | Join voice channel + gossip |
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ Skipped when:
|
|||||||
- **Channel list** — existing unread + `@` pills
|
- **Channel list** — existing unread + `@` pills
|
||||||
- **🔔 header** — inbox panel, prefs, mark all read; **click row jumps to message** (v0.8.35+)
|
- **🔔 header** — inbox panel, prefs, mark all read; **click row jumps to message** (v0.8.35+)
|
||||||
- **Mark all read** — dedicated header button when unread rows exist (v0.8.115); smoke: `npm run test:notif-mark-all-header`
|
- **Mark all read** — dedicated header button when unread rows exist (v0.8.115); smoke: `npm run test:notif-mark-all-header`
|
||||||
|
- **Status describedby target (v0.8.143)** — only the mark-all button references `notif-mark-all-status` via `aria-describedby`; the status banner does not self-reference. Smoke: `npm run test:notif-mark-all-status-describedby-only`
|
||||||
- **Disabled keeps describedby (v0.8.142)** — after mark-all, a disabled header button keeps `aria-describedby="notif-mark-all-status"` when the status banner already exists. Smoke: `npm run test:notif-mark-all-disabled-describedby`
|
- **Disabled keeps describedby (v0.8.142)** — after mark-all, a disabled header button keeps `aria-describedby="notif-mark-all-status"` when the status banner already exists. Smoke: `npm run test:notif-mark-all-disabled-describedby`
|
||||||
- **Restore keeps describedby (v0.8.141)** — when unread returns and the mark-all status banner is still present, `ensureNotifMarkAllHeader` re-links `aria-describedby="notif-mark-all-status"`. Smoke: `npm run test:notif-mark-all-restore-status-describedby`
|
- **Restore keeps describedby (v0.8.141)** — when unread returns and the mark-all status banner is still present, `ensureNotifMarkAllHeader` re-links `aria-describedby="notif-mark-all-status"`. Smoke: `npm run test:notif-mark-all-restore-status-describedby`
|
||||||
- **Restore clears describedby (v0.8.140)** — when unread returns and the status banner is gone, `ensureNotifMarkAllHeader` clears stale `aria-describedby` on the mark-all button. Smoke: `npm run test:notif-mark-all-restore-describedby`
|
- **Restore clears describedby (v0.8.140)** — when unread returns and the status banner is gone, `ensureNotifMarkAllHeader` clears stale `aria-describedby` on the mark-all button. Smoke: `npm run test:notif-mark-all-restore-describedby`
|
||||||
|
|||||||
+13
-4
@@ -4,14 +4,23 @@ Gap analysis between **Pearcord** (P2P Pear app) and a typical **centralized cha
|
|||||||
|
|
||||||
> This is the only doc that compares against an external reference product. All other docs use Pearcord naming only.
|
> This is the only doc that compares against an external reference product. All other docs use Pearcord naming only.
|
||||||
|
|
||||||
|
## Summary (v0.8.143)
|
||||||
|
|
||||||
|
| Area | Reference product | Pearcord | Gap |
|
||||||
|
|------|-------------------|----------|-----|
|
||||||
|
| Search | Recent queries | activedescendant + chip removal | aria-controls link (P179); no cloud |
|
||||||
|
| Channels | Categories | Voice/text label semantics | Forum label count (P179) |
|
||||||
|
| Notifications | Inbox | Button→status describedby only | Banner labelledby (P179) |
|
||||||
|
| Guild open | Channel pick | Triple-bucket minPos tie-break | Mention triple bucket (P179) |
|
||||||
|
|
||||||
## Summary (v0.8.142)
|
## Summary (v0.8.142)
|
||||||
|
|
||||||
| Area | Reference product | Pearcord | Gap |
|
| Area | Reference product | Pearcord | Gap |
|
||||||
|------|-------------------|----------|-----|
|
|------|-------------------|----------|-----|
|
||||||
| Search | Recent queries | Delete + Alt+Delete chip removal | activedescendant (P178); no cloud |
|
| Search | Recent queries | Delete + Alt+Delete chip removal | activedescendant (done P178); no cloud |
|
||||||
| Channels | Categories | Label unread, count, collapsed state | Voice label count (P178) |
|
| Channels | Categories | Label unread, count, collapsed state | Voice label count (done P178) |
|
||||||
| Notifications | Inbox | Mark-all describedby full lifecycle | Status-only smoke (P178) |
|
| Notifications | Inbox | Mark-all describedby full lifecycle | Status-only smoke (done P178) |
|
||||||
| Guild open | Channel pick | Equal minPos uses scored bucket order | Three-bucket minPos (P178) |
|
| Guild open | Channel pick | Equal minPos uses scored bucket order | Three-bucket minPos (done P178) |
|
||||||
|
|
||||||
## Summary (v0.8.141)
|
## Summary (v0.8.141)
|
||||||
|
|
||||||
|
|||||||
+20
-6
@@ -855,16 +855,29 @@ When completing work, change `[ ]` → `[x]` and add a one-line note under **Cha
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Phase 178 — Inbox & guild depth (planned)
|
### Phase 178 — Inbox & guild depth (v0.8.143)
|
||||||
|
|
||||||
| ID | Item | Status |
|
| ID | Item | Status |
|
||||||
|----|------|--------|
|
|----|------|--------|
|
||||||
| P178-1 | Search: chip listbox `aria-activedescendant` for roving focus | [ ] |
|
| P178-1 | Search: chip listbox `aria-activedescendant` for roving focus | [x] |
|
||||||
| P178-2 | Channels: voice category label includes voice channel count | [ ] |
|
| P178-2 | Channels: voice category label includes voice channel count | [x] |
|
||||||
| P178-3 | Notifications: mark-all status `aria-describedby` on banner only smoke | [ ] |
|
| P178-3 | Notifications: mark-all status `aria-describedby` on banner only smoke | [x] |
|
||||||
| P178-4 | Guild: smoke three-way bucket total tie uses minPos | [ ] |
|
| P178-4 | Guild: smoke three-way bucket total tie uses minPos | [x] |
|
||||||
|
|
||||||
**Next:** Chip activedescendant · voice label count · status describedby smoke · three-bucket minPos.
|
**Next:** ~~Chip activedescendant · voice label count · status describedby smoke · three-bucket minPos~~ (Phase 178).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 179 — Search & guild polish (planned)
|
||||||
|
|
||||||
|
| ID | Item | Status |
|
||||||
|
|----|------|--------|
|
||||||
|
| P179-1 | Search: listbox `aria-controls` links search field to chips | [ ] |
|
||||||
|
| P179-2 | Channels: forum category label includes forum count | [ ] |
|
||||||
|
| P179-3 | Notifications: mark-all banner `aria-labelledby` smoke | [ ] |
|
||||||
|
| P179-4 | Guild: smoke mention triple-bucket beats plain totals | [ ] |
|
||||||
|
|
||||||
|
**Next:** Search controls link · forum label · banner labelledby · mention triple bucket.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -2297,6 +2310,7 @@ When completing work, change `[ ]` → `[x]` and add a one-line note under **Cha
|
|||||||
|
|
||||||
### Changelog
|
### Changelog
|
||||||
|
|
||||||
|
- **2026-05-22** — v0.8.143: Phase 178 — Chip activedescendant, voice label, status describedby only, triple bucket minPos (`test:search-recent-chip-activedescendant`, `test:channel-category-voice-label`, `test:notif-mark-all-status-describedby-only`, `test:guild-bucket-minpos-triple-pick`). Phase 179 planned.
|
||||||
- **2026-05-22** — v0.8.142: Phase 177 — Alt+Delete chip, label channel count, disabled describedby, bucket minPos order (`test:search-recent-chip-alt-delete`, `test:channel-category-label-count`, `test:notif-mark-all-disabled-describedby`, `test:guild-bucket-minpos-order-pick`). Phase 178 planned.
|
- **2026-05-22** — v0.8.142: Phase 177 — Alt+Delete chip, label channel count, disabled describedby, bucket minPos order (`test:search-recent-chip-alt-delete`, `test:channel-category-label-count`, `test:notif-mark-all-disabled-describedby`, `test:guild-bucket-minpos-order-pick`). Phase 178 planned.
|
||||||
- **2026-05-22** — v0.8.141: Phase 176 — Field Delete reveal, expanded label polish, restore status describedby, bucket minPos root (`test:search-recent-chip-delete-field-reveal`, `test:channel-category-expanded-label`, `test:notif-mark-all-restore-status-describedby`, `test:guild-bucket-minpos-root-pick`). Phase 177 planned.
|
- **2026-05-22** — v0.8.141: Phase 176 — Field Delete reveal, expanded label polish, restore status describedby, bucket minPos root (`test:search-recent-chip-delete-field-reveal`, `test:channel-category-expanded-label`, `test:notif-mark-all-restore-status-describedby`, `test:guild-bucket-minpos-root-pick`). Phase 177 planned.
|
||||||
- **2026-05-22** — v0.8.140: Phase 175 — Delete reveals then removes chip, collapsed label unread, restore describedby, root mention bucket (`test:search-recent-chip-delete-reveal`, `test:search-recent-remove`, `test:channel-category-collapsed-label-unread`, `test:notif-mark-all-restore-describedby`, `test:guild-root-mention-bucket-pick`). Phase 176 planned.
|
- **2026-05-22** — v0.8.140: Phase 175 — Delete reveals then removes chip, collapsed label unread, restore describedby, root mention bucket (`test:search-recent-chip-delete-reveal`, `test:search-recent-remove`, `test:channel-category-collapsed-label-unread`, `test:notif-mark-all-restore-describedby`, `test:guild-root-mention-bucket-pick`). Phase 176 planned.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Documentation for **[Pearcord](https://git.ssh.surf/pearcord)** — a 100% peer-to-peer community chat app on [Pear](https://pear.holepunch.to). No central message servers.
|
Documentation for **[Pearcord](https://git.ssh.surf/pearcord)** — a 100% peer-to-peer community chat app on [Pear](https://pear.holepunch.to). No central message servers.
|
||||||
|
|
||||||
**Current release:** v0.8.142 (desktop app `apps/pearcord`).
|
**Current release:** v0.8.143 (desktop app `apps/pearcord`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ See also [FORUM_ARCHIVE_SEARCH.md](./FORUM_ARCHIVE_SEARCH.md).
|
|||||||
|
|
||||||
**v0.8.115:** Guild-scoped recent queries persist in `userPrefs.searchRecentByGuild[guildId]` (max 8, deduped). `setSearch` records queries ≥2 chars. Desktop shows **recent chips** under the search input when scope is `guild` and the field is empty. Smokes: `npm run test:search-recent-prefs`, `npm run test:members-online-sort` (UI wiring).
|
**v0.8.115:** Guild-scoped recent queries persist in `userPrefs.searchRecentByGuild[guildId]` (max 8, deduped). `setSearch` records queries ≥2 chars. Desktop shows **recent chips** under the search input when scope is `guild` and the field is empty. Smokes: `npm run test:search-recent-prefs`, `npm run test:members-online-sort` (UI wiring).
|
||||||
|
|
||||||
|
**v0.8.143:** Recent-chip listbox sets `aria-activedescendant` to the focused option id (`search-recent-opt-N`) on roving focus. Smoke: `npm run test:search-recent-chip-activedescendant`.
|
||||||
|
|
||||||
**v0.8.142:** **Alt+Delete** removes a recent chip without clearing an active filter (field or listbox). Plain **Delete** still reveals all chips first. Smoke: `npm run test:search-recent-chip-alt-delete`.
|
**v0.8.142:** **Alt+Delete** removes a recent chip without clearing an active filter (field or listbox). Plain **Delete** still reveals all chips first. Smoke: `npm run test:search-recent-chip-alt-delete`.
|
||||||
|
|
||||||
**v0.8.141:** **Delete** from the search field with an active chip filter clears the filter, removes the first visible recent query, and keeps roving focus on the next chip. Smoke: `npm run test:search-recent-chip-delete-field-reveal`.
|
**v0.8.141:** **Delete** from the search field with an active chip filter clears the filter, removes the first visible recent query, and keeps roving focus on the next chip. Smoke: `npm run test:search-recent-chip-delete-field-reveal`.
|
||||||
|
|||||||
Reference in New Issue
Block a user