initial
This commit is contained in:
563
PRD.md
Normal file
563
PRD.md
Normal file
@@ -0,0 +1,563 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user