Handbook
Teams Web read strategies
Read-only harvest paths for forge-lcdl Teams tasks. All paths use an attached Playwright session (prefer Edge CDP with a signed-in M365 profile).
Updated
Consumer orchestration (checkpoints, unified_message, sync UI) lives in the host app — see Messaging consumer boundary.
Strategy selection
| Goal | Governed task | Notes |
|---|---|---|
| Gate before sync | teams_probe_health |
Surface readiness |
| Cross-chat date window | teams_search_messages_chunk |
Global search: *, Messages scope, date from/to, Next until disabled, keyboard navigation to open highlighted row |
| Chat inventory | teams_list_chats |
Sidebar list; may return 0 when rail not harvestable |
| Channel inventory | teams_list_channels |
Team channels |
| Bulk chat messages (chunked) | teams_list_chat_messages_chunk |
chunk_size + checkpoint / next_cursor; scroll direction up for older history |
| Channel post batches | teams_list_channel_posts_chunk |
Team/channel threads |
| Thread replies | teams_read_thread_replies |
Under a post URL |
Recommended consumer loop (chat ingest)
Date-window / multi-chat (production primary)
When the consumer sets a harvest window (e.g. past 7 days) or teams_list_chats is empty:
teams_probe_healthwithsession_refand Teams v2 URL.teams_search_messages_chunk— open global search (AUTOSUGGEST_INPUTor Ctrl+Alt+E), query*, select Messages, applydate_from/date_to, paginate with Next until disabled, activate each result (keyboard or click) to open the highlighted message, harvest row fields. Prefer composing from public atoms inconnectors/teams_search_atoms.py(scope verify + per-page guard). Optional discover-only probe:teams_probe_day_pagination(cockpit:scripts/run-teams-search-pagination-harness.pyor G0scripts/run-teams-search-atoms-smoke.py).- Map
items[]→ consumer model; dedupe on Exchangeitem_id/message_identity_key. - Fall back to sidebar chat loop (below) for rows search missed.
Sidebar chat loop (fallback)
teams_probe_healthwithsession_refand channelentry_url(Teams v2).- Optional:
teams_list_chats— iterate up tomax_chats. teams_list_chat_messages_chunkwithchunk_size40–100;scroll_direction=upfor date windows; passnext_cursorascheckpointuntilchunk.has_moreis false.- Map each
items[]row → consumer message model; dedupe on stable id (data-mid/item_idwhen present). - Optional:
teams_read_thread_replieswhen list rows lack full body text.
Not a substitute for global search: per-chat Find in chat (Ctrl+F) only searches the open conversation.
Keyboard navigation (production default: COCKPIT_TEAMS_NAV_MODE=keyboard)
Accessible keyboard paths — no mouse clicks or programmatic scroll unless keyboard step fails (click mode is legacy/debug only).
| Step | Keyboard | Fallback (COCKPIT_TEAMS_NAV_MODE=click) |
|---|---|---|
| Open search | Ctrl+Alt+E → focus AUTOSUGGEST_INPUT |
Toggle search button |
| Messages tab | search_focus_messages_tab → Enter |
search_click_messages_tab |
| Query sync | Focus input → Ctrl+A → * → Enter |
Same with click-to-focus |
| Date chip | Focus [data-tid="search-date-filter"] → Enter/Space |
search_prepare_date_panel click |
| From / To | Keyboard type Jun D, YYYY → Enter → Tab (no calendar clicks) |
Calendar pick + input fallback |
| Apply | search_focus_date_apply → Enter |
search_click_date_apply |
| SERP load | Focus results → PageDown / End (search_keyboard_scroll_step) |
search_scroll_step (scrollTop) |
| SERP row | Focus role="row" + Enter |
search_click_card |
| Next page | Focus Next + Enter |
search_click_next |
LCDL module: connectors/teams_search_keyboard.py. Chunk stats include nav_method in chunk_meta warnings.
Production default: Cockpit ladder/EOD scripts export COCKPIT_TEAMS_NAV_MODE=keyboard. Before re-running EOD/ladder, stop in-flight harvest: ./scripts/stop-teams-search-ingest.sh (Cockpit). Consumer applies serp_day_scoped ingest trust when the date chip matches the scan day — see Cockpit teams-search-day-ingest.md.
Search ingest atoms (teams_search_atoms.py)
Test-first public API — compose EOD/remediation without importing private teams_search_chunk helpers:
| Atom | Function |
|---|---|
| Surface | teams_ensure_search_surface |
| Open | teams_open_messages_search |
| Scope | teams_apply_single_day_scope |
| Verify | teams_verify_single_day_scope → ScopeVerdict (date chip canonical; query-day scope is drift) |
| Guard | teams_guard_single_day_scope (re-apply + FORGE_LCDL_TEAMS_MAX_CARDS_PER_DAY) |
| Prepare | teams_prepare_serp_surface |
| Harvest | teams_harvest_serp_cards |
| Paginate | teams_advance_serp_next_page |
| Detail | teams_harvest_card_detail |
| Restore | teams_restore_serp_after_detail |
| Probe | teams_probe_day_pagination |
CI gate: pytest tests/playwright/test_teams_search_atoms_*.py before live EOD (run-teams-search-last-week-eod.sh).
Read-only policy
Same session contract as Outlook: navigate, evaluate_readonly, scroll_readonly, limited click_readonly for list focus only. No send, react, call, or meeting joins. See READONLY_POLICY.md.
CDP setup
Same as Outlook: consumer starts Edge with remote debugging, signs in to https://teams.microsoft.com/v2/, registers the page, then calls LCDL tasks. Cockpit documents this in its messaging boundary handbook.
Tab visibility: Teams virtualized chat lists behave like OWA — background tabs may not render rows. LCDL enables CDP focus emulation on attach when bring-to-front is off (FORGE_LCDL_CDP_BRING_TO_FRONT=0, COCKPIT_EDGE_FOCUS_EDGE=0). Do not rely on page.bring_to_front() for production ingest. See EDGE-CDP-ATTACH.md.
Timing
| Task | Typical live |
|---|---|
teams_probe_health |
5–30 s (navigation + settle) |
teams_list_chat_messages_chunk |
30 s – several min per chunk (virtualized list) |
Consumers should emit heartbeat progress during long run_task calls so operators do not assume a stuck browser step.
Example consumer bridge (reference only)
forge-cockpit-web implements cockpit_server.lcdl_teams_bridge (harvest_chat_chunk_via_lcdl, run_task). LCDL does not import that module.