566 lines
18 KiB
Markdown
566 lines
18 KiB
Markdown
# Gitlore - Product Requirements Document
|
|
|
|
> **Note:** The project was renamed from "gitlab-inbox" to "gitlore" and the CLI from "gi" to "lore".
|
|
|
|
## Overview
|
|
|
|
**Product Name**: Gitlore (formerly 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
|