Skip to content

Add session tags, personal filters and tag-aware launcher search - #118

Closed
jeroendee wants to merge 9 commits into
TannerMidd:mainfrom
jeroendee:feature/session-tags-and-filters
Closed

jeroendee wants to merge 9 commits into
TannerMidd:mainfrom
jeroendee:feature/session-tags-and-filters

Conversation

@jeroendee

@jeroendee jeroendee commented Oct 21, 2026 •

Copy link
Copy Markdown

Summary

  • Add shared session tags with single-session editing and atomic batch add/remove.
  • Add personal AND filters with searchable, bounded choices and independently removable selections. New sessions started from the filtered Sessions list inherit its active tags.
  • Find sessions by tag in the launcher; improve narrow-list titles, actions and search wording.

Proof

Original feature evidence: a5774cc (current PR head: 7a264a3). Real isolated app on Linux/Chromium; 19 synthetic sessions seeded with 133 tags and a scripted model. No mocked HTTP responses or production data.

Scenario / expectation Observed result Evidence
Single/batch edits persist without replacing unrelated tags or changing recency Both targets updated; original labels and timestamps preserved 20s desktop demo; API result below
Tag search opens the right session; personal filtering remains separate Launcher opens the conversation/composer; filter shows exactly 2 matching sessions Desktop demo and screenshots
Large vocabularies remain usable Search/no matches, hidden-selection removal and stationary actions work 16s phone demo; 390×844 / 320×500 captures
Invalid batches cannot partially save Overflow → 409, neither target changes; missing X-Pocket → 403 Actual API checks below

Original validation (a5774cc): npm run check, npm run lint, npm test → 422/422 pass, zero failures/skips. Format, syntax and whitespace checks pass. The committed files match the validated tree.

20-second end-to-end demo:

end-to-end-20s.mp4
API contract and actual response

Authenticated POST /api/sessions/tags: positive-integer ids[]; optional add[] / remove[] names. Names are trimmed/lowercased, 1–32 characters; at most 10 tags per session. Success is {ok:true}; errors are {error:string}.

Actual browser request and response:

POST /api/sessions/tags
Content-Type: application/json
X-Pocket: 1

{"ids":[14,3],"add":["reviewed"],"remove":["shipping"]}
→ 200 {"ok":true}

Subsequent GET /api/sessions confirms both gained reviewed, only session 3 lost shipping, and other labels/updatedAt survived. A batch exceeding 10 tags returned 409 {"error":"A selected session would have more than 10 tags. Nothing was saved."}; before/after metadata matched. Without X-Pocket: 403 {"error":"Missing X-Pocket header"}, no mutation.

Screenshots of changed screens and resulting state

All at a5774cc; phone captures use the identical precommit tree.

Session list with tags and native hover actions
Session actions with Edit tags entry
Single-session tag editor with queued addition
Two selected sessions and text-only batch toolbar
Two-session batch editor with explicit add/remove drafts
Launcher finds both sessions by their reviewed tag
Launcher result opens the actual conversation and composer
Filter vocabulary search retains the selected reviewed chip
Done leaves exactly two matching sessions
Phone search retains an independently removable selected tag
Constrained-height filter keeps actions reachable

16-second phone journey:

filter-phone-16s.mp4
Before → after regressions

List title space: 38979b5 → a32e21f (254px list).
Before and after title-space correction

Search wording: a92cd21 → 6beba9d (unchanged sidebar width).
Before and after full search wording

Filter inventory: 6beba9d → a5774cc (390×844, same synthetic vocabulary).
Before unbounded checklist and after bounded searchable filter

New sessions inherit active tags

At 7a264a3, select tags → Done → Sessions New → folder picker Open creates a session with all selected tags, saved in the initial creation transaction. In the actual native journey, POST /api/sessions with tags: ["polish", "review"] returned HTTP 200 and opened session 29; its stored metadata and matching list row contained both tags. No follow-up tag patch was needed.

Creation regressions pass at desktop, 390×844 and 320×500, including reload persistence, unchanged no-filter behavior, and rejection of 11 tags without partial creation. Backend tests also verify restart persistence. The complete current candidate passed 439/439 tests, zero failures/skips; exact-revision type/lint and regression checks pass.

New session inherits both active tag filters and remains in the matching list

session-tags-polish-22s.mp4

Local isolated app, real routes/assets and synthetic data; phone sizes use Chromium emulation. Other text/view constraints can still hide a new session.

Limits: local/emulated Chromium, not production, physical-device or screen-reader acceptance. At 320×500, ~3px of the results' lower border initially clips; ~7px outer scrolling reveals it, with all actions reachable. Capture setup needed selector/timing corrections and one aborted-reload retry; the final recorded journey passes.

Store normalized shared tags in existing session metadata and apply permission-checked Add/Remove patches atomically without changing recency.

Add personal match-all filters and native single/batch tag editors while preserving existing search, views and mobile hold actions.

Cover validation, authorization, concurrency, persistence and responsive UI regressions; document the feature and storage model.
Reclaim idle title space without moving the opening target, paint the complete session item, and separate tags from the title. Preserve Lantern styling and use a text-only batch Edit tags action. Add real-browser regressions for layout, hit testing, themes, and action consistency.
# Conflicts:
#	docs/features.md
#	docs/map.md
TannerMidd added a commit that referenced this pull request Oct 21, 2026
…actions

Adds four experimental pocket members, and WebRequest.sessions on the server:
- pocket.sessionChips: chips under a row's title; a chip with run is a button.
- pocket.sessionSearch: words that the list's search and the launcher match.
- pocket.sessionFilter: a button in the search field; while match is set, the
  list shows only what every filter that is on keeps.
- pocket.sessionAction: an action in a held row's sheet and the selection bar.
- request.sessions(): the sessions the person can see, so routes check ids with
  core's own rule.

A function that throws stops its module, and the list draws without it.
Theme parts: session-chips, session-chip, session-filter.

From the review of #118 by Jeroen Dee (@jeroendee): the mechanism goes to core
as extension points (C7), and tags become an extension.
TannerMidd added a commit that referenced this pull request Oct 21, 2026
Shared tags on sessions, built only on host, web, and the session list's
extension points: chips on rows, a tag filter beside the search (a tap on a
chip filters too), tags in the search and the launcher, and Edit tags for a
held row, selected rows, or the open session. Tags are lowercase, 32
characters, 10 a session at most; a change is all or nothing. Routes check ids
with request.sessions(), and each person gets only the tags of sessions they
see. Viewers read and filter; people invited to one session do not load it.

Based on #118 by Jeroen Dee (@jeroendee), which first put tags in core.
Thanks to Jeroen Dee for the feature, its design, and its limits.

Tested: test/session-tags.test.ts, test/session-list-extensions-ui.test.ts,
npm run check, lint, format, docs-site check, and the full npm test.
TannerMidd added a commit that referenced this pull request Oct 21, 2026
Splits #118 by Jeroen Dee (@jeroendee): the session list gets experimental
extension points, and tags become a built-in extension, off by default.
Thanks to Jeroen Dee for the feature and its design.
TannerMidd added a commit that referenced this pull request Oct 21, 2026
- sessionAction takes icon or iconPath: the first such action is also a button
  on rows, beside archive, pin, and mark unread. Session tags' Edit tags is one.
- sessionFilter takes started(id): New in the Sessions head, while the filter
  is on, gives the new session to it. Session tags tags it with the chosen
  tags, so it stays in the filtered list.
- Session tags shows what a save answered at once, and looks again when the
  tab comes back: an event sent while the tab reconnects is lost.
- On a server older than 0.20.0 the module adds nothing. Such a server loads
  the file when its checkout moves, and would turn it on without the core it
  needs. A server that runs 0.20.0 turns it on and off with no restart.

Both are from #118 by Jeroen Dee (@jeroendee).
Tested: npm run check, lint, the new and changed tests, and the full npm test
(395 of 397; the 2 timing tests that failed under load pass alone).
@TannerMidd

Copy link
Copy Markdown
Owner

Thank you, @jeroendee! This is a great feature, and your PR made the case for it clearly. Tags ship in 0.20.0, in a different shape from this PR, so I'm closing it.

Why the change: under our core-or-extension policy, tags are a way to organize work that only some people want (C6). The session list's mechanism goes into core instead, and the tags workflow becomes an extension on top of it (C7). Other extensions can then use the same hooks for status labels, priorities, and similar.

What is on release/0.20.0:

  • Core, as experimental extension points:
    • pocket.sessionChips: chips under a row's title.
    • pocket.sessionSearch: words that the sidebar search and the launcher match.
    • pocket.sessionFilter: a filter button in the search field. Its started(id) runs when New starts a session from the filtered list.
    • pocket.sessionAction: an action in a held row's sheet and the selection bar. With an icon, it is also a hover button on rows.
    • request.sessions() on the server: routes check session ids with core's own visibility rule.
  • Session tags, a built-in extension that is off by default (src/server/extensions/session-tags.ts). It uses only host, web, and those points. Your limits are kept: lowercase tags of up to 32 characters, 10 per session, and all-or-nothing batch saves. It also has tag chips on rows, AND filtering, tag search in the sidebar and the launcher, a hover tag button, Edit tags for a held row or selected rows, and new sessions that get the active filter's tags. Viewers can read and filter tags, and people invited to one session don't see them.

Tags are stored in the extension's own document, not on SessionMeta. So tagging can't touch updatedAt or the order in Recent, and turning the module off leaves the session list untouched.

You're credited in the commits (668c65b, e191b32, d8b14c4, 0aa7195) and in the policy doc's example section. If you try the release branch and something from your design is missing or feels off, please open an issue. Thanks again!

@TannerMidd TannerMidd closed this Oct 21, 2026
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.

2 participants