6.7 KiB
Research decks
Workspace interface
The deck occupies the viewport with a dark sidebar and horizontally arranged, independently scrolling columns. A compact toolbar names the active deck. The sidebar switches decks, adds columns, and jumps to a column; on mobile, it becomes a compact top bar. Column header menus expose editing, ordering, and deletion. Creation and editing use native modal dialogs with Escape and focus restoration. Tokens use a navy/blue palette and a system sans font. This replaces the original spacious Garden reader layout. Layout inspiration: Twitter's TweetDeck design notes.
Product direction
A research topic becomes a TweetDeck-style workspace: columns represent questions or perspectives and will eventually collect posts across Twitter, Mastodon, Bluesky, Threads, and Nostr for AI summaries with source references. The current implementation supports Twitter only, with manually or WebMCP-authored decks. Built-in planning, summaries, and other connectors remain future work.
Workspace and column model
Both / and /deck render the same deck-only application. Separate reader,
search, list, user-profile, and conversation pages have been removed.
Original-post links open X.
src/features/decks/model.ts validates a version-2 workspace containing
activeDeckId and one or more named decks. Each deck has a stable ID and up to
six ordered columns. Only the active deck mounts its columns. The UI supports
creating, selecting, renaming, and deleting deck profiles; the last deck cannot
be deleted. Deleting the active deck selects the first remaining deck.
Each column has a stable ID, title, required profileName, and one source:
| Source kind | Conditions |
|---|---|
search |
Native Twitter query, product (Top/Latest), and following |
user |
target: handle or X/Twitter profile URL, normalized to a handle |
list |
target: numeric ID or X/Twitter list URL, normalized to an ID |
All sources currently require platform: "twitter". Unsupported definitions
fail validation. Column IDs must be unique within a deck, and deck IDs within
the workspace. Manual edits and agent tools use the same final schema.
profileName is the relay account binding, distinct from a named deck profile.
The editor discovers names through /profiles and lists through the selected
profile. A column's profile can be changed independently. Every feed and list
request carries an explicit profile name; the server confirms it still exists.
Deleted profiles and unavailable discovery produce errors instead of falling
back to another account. There is no browser-wide profile selection.
The complete source and profile participate in query-cache identity. Equal conditions on the same profile share loaded pages; distinct profiles retain separate results and cursors. Columns have independent refresh, pagination, and error state. Pagination and refresh are manual, with no polling.
Platform boundary
src/features/platforms/types.ts defines the display/evidence record without
Bird imports: stable key, platform, native identity, original URL, text,
author, optional publication time, media, and quoted post. The Twitter mapper
uses twitter:<id> keys rather than mutable author handles. Raw responses
and credentials do not enter this record. Cards consume the normalized record;
engagement metrics and Twitter article previews are not normalized yet.
The source union is the extension point for future connectors. Twitter search syntax and ranking controls are provider-specific. When implementing another connector, add its real schema and server operation, normalize stable identity, and bind connection details into cache identity. Multiple sources in one column should wait until a second connector exercises that need; each source must retain its own opaque continuation and error state.
Persistence
The workspace is stored under twitter-lite-research-deck in localStorage,
including all deck definitions and the active selection. It stores conditions
and relay profile names, not credentials, posts, summaries, or cursors.
Reloading fetches first pages of the selected deck. SSR and the first browser
render show a loading state until storage has been read.
There is no automatic migration of the former single-deck format, which did not pin profiles to columns. Invalid or older saved data remains untouched while the UI presents an empty workspace and an error. An explicit saved edit replaces it. Storage failures are visible: changes still apply in the current tab, but persistence failures mean they will be lost on reload. Tabs and devices do not synchronize; the last write to an origin's localStorage wins. HTTP and HTTPS origins maintain separate workspaces.
Deck WebMCP tools
Both deck routes expose workspace management and active-column reading.
list_decks discovers definitions and available relay profiles; get_deck
reads a specific or active deck. set_deck creates or replaces and activates a
deck; select_deck and delete_deck operate by ID. get_column_posts and
load_more_column read or paginate columns in the active deck. See the
full tool contracts.
For example, after discovering a relay profile named main, create a deck:
{
"title": "WebMCPの反応",
"columns": [
{
"title": "日本語",
"profileName": "main",
"source": { "kind": "search", "query": "WebMCP lang:ja" }
},
{
"title": "開発者",
"profileName": "main",
"source": { "kind": "user", "target": "@example" }
}
]
}
Omitting deckId creates a deck. To edit, read first and include its deckId
and every column to retain; keep existing column IDs. Omitted columns are
removed and omitted column IDs are generated. Post loading is asynchronous,
so a successful save does not mean the upstream requests succeeded.
Future AI work
A planner can generate definitions through the existing schema and tools. Summaries will need persisted collection snapshots: post identity and URL, retrieval time, source conditions, and profile context. Saved conditions alone do not preserve the evidence behind a summary. ACP or Codex app-server may connect a future planner, but neither is part of this implementation.
Verification
Unit tests cover normalization, source/workspace validation, ordering, profile-specific caching and pagination, profile discovery failures, and storage behavior. Playwright uses a standalone mock relay through real server functions to exercise deck switching, column/profile editing, list selection, pagination, persistence, and native WebMCP. Accessibility checks run on the workspace. Automated tests do not need a live SNS search.