18 KiB
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
- Know immediately when someone has replied or needs my attention
- Quickly navigate to the right place in GitLab to respond
- Track what I've handled today for satisfaction and progress awareness
- Reduce cognitive load of manually tracking conversations
- Defer items temporarily without losing accountability (snooze)
- Know when someone has replied to something I'm waiting on
Product Goals
- Reduce time-to-awareness for GitLab notifications
- Eliminate the need to manually poll GitLab for updates
- Provide ADHD-friendly UX patterns (clear actions, progress visibility, minimal decisions)
- 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-Afterheader; 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):
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
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 tostate.json - Keep
state.json.bakas 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
- Open GitLab Inbox (already running in background tab)
- See list of todos sorted by newest first
- Press
Enterto open in GitLab - Handle the item (reply, review, etc.)
- Return to Inbox, press
hto mark handled - Item moves to Done Today
- Repeat until Inbox is empty
Flow 2: Triage with Snooze
- See inbox with 12 items
- Quickly triage: handle 3, snooze 5 until tomorrow, dismiss 2 already-resolved
- Inbox now shows 2 items to focus on
- Tomorrow: snoozed items wake up and return to inbox
Flow 3: Awaiting Reply
- Handle a todo (you replied to someone's question)
- Toggle "Add to Watchlist" when marking handled
- Item appears in Watchlist view
- Later: see "New activity" indicator when they respond
- Open, read response, remove from watchlist
Flow 4: Focus Session
- Enable Focus Mode
- See only top 3 items
- Work through them sequentially
- As items complete, next ones appear
- Reduced decision fatigue
Flow 5: End-of-Day Review
- Navigate to Done Today view
- See all items handled today
- 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_urlfor 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:
GITLAB_URL=https://gitlab.yourcompany.com
GITLAB_TOKEN=glpat-xxxxxxxxxxxx
Config file structure:
{
"gitlabUrl": "https://gitlab.yourcompany.com",
"pollingInterval": 60,
"focusModeCount": 3
}
Creating a GitLab PAT
- Go to GitLab → User Settings → Access Tokens
- Create token with
read_apiscope - Set expiration (note: tokens expire at midnight UTC on expiry date)
- Save token via setup wizard (stored in keychain)
- 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):
/todosresponse parsingmark_as_doneendpoint 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