# GitLab Inbox - Product Requirements Document ## Overview **Product Name**: GitLab Inbox **Version**: 1.0 **Author**: Taylor Eernisse **Date**: January 16, 2026 ### Problem Statement Managing GitLab activity with ADHD is overwhelming. The native GitLab interface creates cognitive overload through: - **Information scatter**: Issues, MRs, and activity are spread across multiple pages - **Missing reply awareness**: Hard to know when someone has responded to your question (not fully covered by /todos alone) - **Context loss**: Difficult to find the right tab or remember which conversation you were tracking - **No unified "what's next"**: Multiple clicks required to understand what needs attention ### Solution A local, always-open "inbox" application that presents GitLab notifications in an ADHD-friendly interface with explicit "handled" tracking, snooze capabilities, watchlist for awaiting replies, and progress visibility. --- ## Target User **Primary Persona**: Software developer with ADHD working on 1-2 GitLab projects who needs to track conversations and respond to mentions, reviews, and assignments without cognitive overload. **Key Characteristics**: - Needs clear "what's next" visibility - Benefits from external accountability (seeing who's waiting) - Motivated by progress tracking (watching a list shrink) - Prefers always-open tools over on-demand checks - Struggles with context switching and finding the right place - Needs a "not now but not forgotten" path that doesn't require willpower --- ## Goals ### User Goals 1. Know immediately when someone has replied or needs my attention 2. Quickly navigate to the right place in GitLab to respond 3. Track what I've handled today for satisfaction and progress awareness 4. Reduce cognitive load of manually tracking conversations 5. Defer items temporarily without losing accountability (snooze) 6. Know when someone has replied to something I'm waiting on ### Product Goals 1. Reduce time-to-awareness for GitLab notifications 2. Eliminate the need to manually poll GitLab for updates 3. Provide ADHD-friendly UX patterns (clear actions, progress visibility, minimal decisions) 4. Enable keyboard-first operation to reduce friction ### Non-Goals (v1.0) - Replacing GitLab for any write operations (commenting, reviewing, merging) - Supporting multiple GitLab instances - Team/shared usage - Mobile support --- ## Core Features ### 1. Inbox View (Primary) **Description**: Display all GitLab todos (notifications) that need attention. **Data Source**: GitLab `/todos` API endpoint **Display Elements** (per item): | Element | Description | |---------|-------------| | Action Badge | Type indicator: mentioned, assigned, review_requested, build_failed, etc. | | Target Title | MR or Issue title | | Author | Who triggered this todo (name + avatar) | | Time | Relative time since created ("2h ago", "3 days") | | Project | Project name for context | **Interactions**: - **Click item / Enter** → Opens target URL in browser (GitLab) - **Mark Handled** → Moves item to Done Today (local state only) - **Snooze** → Hides item until a chosen time (local state only) - **Dismiss** → `POST /todos/:id/mark_as_done` (marks as done in GitLab) **Filtering**: Items marked as "handled" or "snoozed" locally are hidden from Inbox. ### 2. Snoozed View **Description**: Items temporarily deferred until their wake time. **Purpose**: - "Not now but not forgotten" path - Reduces inbox dread by shrinking the visible list - Enables focus sessions: clear the deck, then pull from Snoozed intentionally **Snooze Options**: - Later today (3 hours) - Tomorrow morning (9am local) - Next weekday (Mon-Fri, 9am) - Custom date/time **Behavior**: - Snoozed items are hidden from Inbox - When wake time passes, item returns to Inbox with a "Woke up" indicator - Snoozed view shows all snoozed items with their wake times ### 3. Watchlist (Awaiting Reply) **Description**: Targets you're explicitly waiting on (MRs/Issues/etc.). Alerts when there is new activity since you last checked. **Purpose**: - GitLab todos don't guarantee "someone replied" notifications - Explicit watch semantics for "I'm waiting on Bob" tracking - Gain "external accountability" symmetry **Data Sources**: - Primary: /todos (fast path for items that generate new todos) - Secondary: per-target `updated_at`/notes polling for watched items (small set) **Interactions**: - Mark Handled → optionally "Add to Watchlist" toggle - Watch item shows "Last seen" timestamp and "New activity" indicator - Click to open target in GitLab - Remove from watchlist when no longer waiting ### 4. Done Today View **Description**: Items marked as handled during the current day. **Purpose**: - ADHD-friendly progress visibility - Satisfaction from watching list shrink - Review of daily accomplishments **Behavior**: - Stored as date-bucketed ledger keyed by local date (YYYY-MM-DD) - "Done Today" shows bucket for current local date - Option to clear today's bucket only - Historical buckets retained for potential "Done Yesterday" or weekly views ### 5. Manual Refresh **Description**: Button to fetch latest todos on demand. **Purpose**: Immediate update when user knows something changed. ### 6. Background Polling (v1.1) **Description**: Automatic periodic refresh of todos. **Configuration**: - Base interval (default: 60s) - Backoff on failure (exponential, capped at 15m) with jitter - 429 handling (respect `Retry-After` header; otherwise back off) **Indicator**: - Last successful refresh time - Next scheduled refresh - Current backoff state (if any) ### 7. Keyboard Shortcuts (v1.0) **Description**: Keyboard-first operation for reduced friction. | Key | Action | |-----|--------| | `j` / `k` | Navigate down / up | | `Enter` | Open selected item in GitLab | | `h` | Mark handled | | `s` | Snooze (opens snooze picker) | | `d` | Dismiss (mark as done in GitLab) | | `w` | Add to / remove from watchlist | | `/` | Focus search/filter | ### 8. Focus Mode (Optional) **Description**: Show only the next N items (default 3) to reduce decision load. **Purpose**: - Convert "overwhelm" into "sequence" - Reduce choices, increase throughput - ADHD-optimized: work the queue, don't manage the list **Behavior**: - Primary action emphasized ("Open", then "Handled"/"Snooze") - Toggle: Focus / All Items - Focus queue is top N unhandled items by creation date --- ## Technical Architecture ### Tech Stack | Component | Technology | |-----------|------------| | Framework | TanStack Start | | Styling | Tailwind CSS | | Runtime | Node.js (local) | | State Persistence | JSON file with atomic writes (local) | | Secret Storage | OS keychain (preferred) or encrypted local store | | GitLab Integration | REST API with Personal Access Token | ### Deployment - **Local only**: Runs on localhost - **No external hosting**: No cloud deployment, no auth flows - **Single user**: No multi-tenancy ### GitLab API **Authentication**: Personal Access Token (PAT) with `read_api` scope **Primary Endpoints**: ``` GET /api/v4/todos?state=pending&per_page=100 POST /api/v4/todos/:id/mark_as_done ``` **Response Structure** (relevant fields): ```typescript interface GitLabTodo { id: number; action_name: | 'assigned' | 'mentioned' | 'build_failed' | 'marked' | 'approval_required' | 'unmergeable' | 'directly_addressed' | 'merge_train_removed' | 'member_access_requested' | string; // forward-compatible for new action types target_type: 'MergeRequest' | 'Issue' | 'Commit' | 'Epic' | 'DesignManagement::Design' | string; target: { id: number; iid: number; title: string; web_url?: string; // optional; may not be present for all target types }; target_url: string; // canonical "Open" URL - use this for navigation author: { id: number; name: string; avatar_url: string; }; project: { id: number; name: string; path_with_namespace: string; }; created_at: string; } ``` ### Local State ```typescript interface LocalState { schemaVersion: number; // for migrations handledByDate: { [localDate: string]: { // YYYY-MM-DD in local time [todoId: number]: { handledAt: string; // ISO timestamp todo: GitLabTodo; // Snapshot for Done Today display } } }; snoozedTodos: { [todoId: number]: { wakeAt: string; // ISO timestamp snoozedAt: string; // ISO timestamp todo: GitLabTodo; // snapshot } }; watchlist: { [watchKey: string]: { // e.g., "MergeRequest:123" or "Issue:456" targetType: string; projectId?: number; targetId: number; targetIid?: number; targetUrl: string; lastSeenUpdatedAt?: string; // ISO - when we last observed the target lastCheckedAt?: string; // ISO - when we last polled addedAt: string; // ISO muted?: boolean; } }; } ``` **Storage**: `~/.config/gitlab-inbox/state.json` **Persistence Strategy**: - Atomic writes: write to `state.json.tmp`, then rename to `state.json` - Keep `state.json.bak` as last-known-good before each write - Validate JSON schema on load; if invalid, fall back to backup and surface warning - Schema version for forward migrations --- ## User Interface ### Layout ``` +--------------------------------------------------+ | GitLab Inbox [Focus] [Refresh] 🟢 2m ago | | [Inbox] [Snoozed] [Watchlist] [Done Today] | +--------------------------------------------------+ | | | > [mentioned] Fix login bug | | Alice Smith · infra-frontend · 2h ago | | [Snooze] [Handle] [Open] | | | | [review_requested] Add caching layer | | Bob Jones · api-service · 1d ago | | [Snooze] [Handle] [Open] | | | | [assigned] Update documentation | | Carol White · docs · 3d ago | | [Snooze] [Handle] [Open] | | | +--------------------------------------------------+ | j/k: navigate Enter: open h: handle s: snooze | +--------------------------------------------------+ ``` ### Action Badge Colors | Action | Color | Meaning | |--------|-------|---------| | mentioned | Blue | Someone mentioned you | | assigned | Purple | Assigned to you | | approval_required | Yellow | Needs your approval | | build_failed | Red | Pipeline failure | | directly_addressed | Cyan | Direct @ mention | | unmergeable | Orange | MR has conflicts | | marked | Gray | Marked as todo | | merge_train_removed | Red | Removed from merge train | | member_access_requested | Teal | Access request | | (unknown) | Gray | Forward-compatible fallback | ### States - **Loading**: Skeleton cards while fetching - **Empty**: "All clear! No pending items." message - **Error**: Connection error with retry button - **Stale**: Visual indicator if data is old (> 5 min since last *successful* refresh) - **Backoff**: Indicator showing retry status when experiencing errors --- ## User Flows ### Flow 1: Morning Check-in 1. Open GitLab Inbox (already running in background tab) 2. See list of todos sorted by newest first 3. Press `Enter` to open in GitLab 4. Handle the item (reply, review, etc.) 5. Return to Inbox, press `h` to mark handled 6. Item moves to Done Today 7. Repeat until Inbox is empty ### Flow 2: Triage with Snooze 1. See inbox with 12 items 2. Quickly triage: handle 3, snooze 5 until tomorrow, dismiss 2 already-resolved 3. Inbox now shows 2 items to focus on 4. Tomorrow: snoozed items wake up and return to inbox ### Flow 3: Awaiting Reply 1. Handle a todo (you replied to someone's question) 2. Toggle "Add to Watchlist" when marking handled 3. Item appears in Watchlist view 4. Later: see "New activity" indicator when they respond 5. Open, read response, remove from watchlist ### Flow 4: Focus Session 1. Enable Focus Mode 2. See only top 3 items 3. Work through them sequentially 4. As items complete, next ones appear 5. Reduced decision fatigue ### Flow 5: End-of-Day Review 1. Navigate to Done Today view 2. See all items handled today 3. Satisfaction from visible progress --- ## Success Metrics | Metric | Target | Measurement | |--------|--------|-------------| | Time to awareness | < 2 min | Time from GitLab event to user seeing it | | Daily items handled | Increased | Compare to baseline (manual tracking) | | Context switches | Reduced | Fewer GitLab tabs open simultaneously | | Snooze usage | Regular | Items snoozed vs dismissed (healthy ratio = snooze used) | | Reply awareness | High | Watchlist items caught before manual check | | User satisfaction | Qualitative | Does this reduce ADHD-related friction? | --- ## Risks and Mitigations | Risk | Impact | Mitigation | |------|--------|------------| | GitLab API rate limits | Polling blocked | Configurable interval, backoff + jitter, respect 429/Retry-After | | Token expiration/rotation | App stops working | Clear error state + setup flow; surface expiry guidance and re-auth path | | State file corruption | Lose handled/snoozed/watch state | Atomic writes (tmp+rename), schema validation on load, keep last-known-good backup | | GitLab API changes | App breaks | Pin to known API version, monitor deprecations, forward-compatible types | | Token leakage | Security incident | Store in OS keychain, not in repo-adjacent files | --- ## Future Considerations (Post v1.0) - **Grouping**: By project, by action type - **Stale highlighting**: Visual alert for items waiting > X days - **Desktop notifications**: OS-level alerts for new high-priority items - **Quick actions**: Approve MR, close issue directly from app - **Multiple GitLab instances**: Connect to both gitlab.com and self-hosted - **Done history**: View handled items from yesterday, this week --- ## Implementation Phases ### Phase 0: Setup & Auth - First-run setup wizard (GitLab URL + token) - Token storage implementation (keychain/encrypted local) - Connectivity check (`/todos`, auth failure UX) - Clear error states for invalid/expired tokens ### Phase 1: Foundation - Initialize TanStack Start project - Set up Tailwind CSS - Create GitLab API client with PAT auth - Fetch and display todos in basic list (using `target_url` for navigation) - Implement click-to-open ### Phase 2: Core Workflow - Add local storage with atomic writes + backup - Implement date-bucketed handled state - Implement "Mark Handled" action - Create Done Today view - Add keyboard shortcuts (minimal set: j/k/Enter/h/s/d) - Add Snooze + Snoozed view - Filter handled/snoozed todos from Inbox ### Phase 3: Reliability & Awareness - Background polling with configurable interval - Backoff/jitter + 429 handling - Last successful refresh tracking - Watchlist ("Awaiting Reply") implementation - Per-target polling for watched items (small set) - Add manual refresh button - Relative time display - Action type badges with colors - Loading and error states - Connection status indicator ### Phase 4: Polish - Focus Mode implementation - Snooze time picker refinement - Keyboard shortcut help overlay - State migration handling (schemaVersion) - Edge case handling (DST, timezone changes) --- ## Appendix ### Environment Configuration **Primary configuration**: - URL + settings in: `~/.config/gitlab-inbox/config.json` - Token stored in OS keychain (preferred) **Optional (dev-only) `.env.local` support**: ```env GITLAB_URL=https://gitlab.yourcompany.com GITLAB_TOKEN=glpat-xxxxxxxxxxxx ``` **Config file structure**: ```json { "gitlabUrl": "https://gitlab.yourcompany.com", "pollingInterval": 60, "focusModeCount": 3 } ``` ### Creating a GitLab PAT 1. Go to GitLab → User Settings → Access Tokens 2. Create token with `read_api` scope 3. Set expiration (note: tokens expire at midnight UTC on expiry date) 4. Save token via setup wizard (stored in keychain) 5. Token never leaves local machine ### Project Structure ``` gitlab-inbox/ ├── app/ │ ├── routes/ │ │ ├── __root.tsx │ │ ├── index.tsx # Inbox view │ │ ├── snoozed.tsx # Snoozed view │ │ ├── watchlist.tsx # Watchlist view │ │ ├── done.tsx # Done Today view │ │ └── setup.tsx # First-run setup │ ├── components/ │ │ ├── TodoCard.tsx │ │ ├── TodoList.tsx │ │ ├── ActionBadge.tsx │ │ ├── Header.tsx │ │ ├── SnoozePicker.tsx │ │ ├── FocusMode.tsx │ │ └── KeyboardHelp.tsx │ ├── lib/ │ │ ├── gitlab.ts # API client │ │ ├── storage.ts # Atomic state persistence │ │ ├── keychain.ts # Token storage │ │ ├── polling.ts # Polling state machine │ │ ├── snooze.ts # Snooze logic + wake checking │ │ ├── watchlist.ts # Watchlist polling │ │ └── types.ts │ └── app.tsx ├── package.json ├── tailwind.config.ts └── vite.config.ts ``` ### Test Strategy **Unit Tests**: - State normalization and migration - Snooze wake time calculations - Date bucketing logic (timezone handling) - Polling backoff calculations **Integration Tests** (mocked GitLab API): - `/todos` response parsing - `mark_as_done` endpoint calls - Error handling (401, 429, network errors) - State persistence round-trip (write + read) - Backup recovery on corruption **Manual Testing**: - First-run setup flow - Keyboard navigation - Snooze + wake cycle - Watchlist activity detection