difit 1.0.8 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,52 +11,71 @@ A lightweight command-line tool that spins up a local web server to display Git
11
11
  - 🌙 **GitHub-like UI**: Familiar dark theme file list and diff interface
12
12
  - 💬 **Inline Comments**: Add comments to specific lines and generate Claude Code prompts
13
13
  - 🔄 **Side-by-Side & Inline Views**: Choose your preferred diff viewing mode
14
+ - 🖥️ **Terminal UI Mode**: View diffs directly in your terminal with `--tui` flag
14
15
  - ⚡ **Zero Config**: Just run `npx reviewit <commit>` and it works
15
16
  - 🔐 **Local Only**: Never exposes data over network - runs on localhost only
16
17
  - 🛠️ **Modern Stack**: React 18 + TypeScript + Tailwind CSS
17
18
  - 🎨 **Syntax Highlighting**: Dynamic language loading for Bash, PHP, SQL, Ruby, Java, and more
18
19
  - ✨ **100% vibe coding**: Built with pure coding energy and good vibes
19
20
 
20
- ## 📦 Installation
21
+ ## Quick Start
21
22
 
22
23
  ```bash
23
- # Global install
24
- npm install -g reviewit
25
-
26
- # Or use npx (no installation needed)
27
- npx reviewit <commit-ish>
24
+ npx reviewit # View HEAD commit changes in a beautiful diff viewer
28
25
  ```
29
26
 
30
27
  ## 🚀 Usage
31
28
 
29
+ ### Basic Usage
30
+
32
31
  ```bash
33
- # Review a specific commit
34
- reviewit 6f4a9b7
32
+ npx reviewit <commit-ish> # View single commit diff
33
+ npx reviewit <commit-ish> [compare-with] # Compare two commits/branches
34
+ ```
35
35
 
36
- # Review HEAD~3
37
- reviewit HEAD~3
36
+ ### Single commit review
38
37
 
39
- # Review uncommitted changes (working directory)
40
- reviewit .
38
+ ```bash
39
+ npx reviewit 6f4a9b7 # Specific commit
40
+ npx reviewit HEAD^ # Previous commit
41
+ npx reviewit feature # Latest commit on branch
42
+ ```
41
43
 
42
- # Custom port, don't auto-open browser
43
- reviewit 6f4a9b7 --port 4300 --no-open
44
+ ### Compare two commits
44
45
 
45
- # Via npx
46
- npx reviewit main~1
47
- npx reviewit . # uncommitted changes
46
+ ```bash
47
+ npx reviewit HEAD main # Compare HEAD with main branch
48
+ npx reviewit feature main # Compare branches
49
+ npx reviewit . origin/main # Compare working directory with remote main
48
50
  ```
49
51
 
50
- ### ⚙️ CLI Options
52
+ ### Special Arguments
53
+
54
+ ReviewIt supports special keywords for common diff scenarios:
51
55
 
52
- | Flag | Default | Description |
53
- | -------------- | ---------- | ---------------------------------------------------- |
54
- | `<commit-ish>` | (required) | Any Git reference: hash, tag, HEAD~n, branch, or `.` |
55
- | `--port` | auto | Preferred port; falls back if occupied |
56
- | `--no-open` | false | Don't automatically open browser |
57
- | `--mode` | inline | Diff mode: `inline` or `side-by-side` |
56
+ ```bash
57
+ npx reviewit # HEAD commit changes
58
+ npx reviewit . # All uncommitted changes (staged + unstaged)
59
+ npx reviewit staged # Staged changes ready for commit
60
+ npx reviewit working # Unstaged changes only (cannot use compare-with)
61
+ ```
62
+
63
+ | Keyword | Description | Compare-with Support |
64
+ | --------- | ------------------------------------------------------ | -------------------- |
65
+ | `.` | Shows all uncommitted changes (both staged & unstaged) | ✅ Yes |
66
+ | `staged` | Shows staged changes ready to be committed | ✅ Yes |
67
+ | `working` | Shows unstaged changes in your working directory | ❌ No |
68
+
69
+ ### ⚙️ CLI Options
58
70
 
59
- > **Note**: Use `.` as the commit-ish to review uncommitted changes in your working directory!
71
+ | Flag | Default | Description |
72
+ | ---------------- | ------------ | ------------------------------------------------------------------- |
73
+ | `<commit-ish>` | HEAD | Any Git reference: hash, tag, HEAD~n, branch, or Special Arguments |
74
+ | `[compare-with]` | (optional) | Optional second commit to compare with (shows diff between the two) |
75
+ | `--port` | auto | Preferred port; falls back if occupied |
76
+ | `--no-open` | false | Don't automatically open browser |
77
+ | `--mode` | side-by-side | Diff mode: `inline` or `side-by-side` |
78
+ | `--tui` | false | Use terminal UI mode instead of web interface |
60
79
 
61
80
  ## 💬 Comment System
62
81
 
@@ -104,15 +123,10 @@ pnpm install
104
123
 
105
124
  # Start development server (with hot reload)
106
125
  # This runs both Vite dev server and CLI with NODE_ENV=development
107
- pnpm run dev # defaults to HEAD
108
- pnpm run dev HEAD~3 # review HEAD~3
109
- pnpm run dev main # review main branch
110
-
111
- # For development CLI only (connects to separate Vite server)
112
- pnpm run dev:cli <commit-ish>
126
+ pnpm run dev
113
127
 
114
128
  # Build and start production server
115
- pnpm run start HEAD
129
+ pnpm run start <commit-ish>
116
130
 
117
131
  # Build for production
118
132
  pnpm run build
@@ -129,20 +143,27 @@ pnpm run typecheck
129
143
  ### Development Workflow
130
144
 
131
145
  - **`pnpm run dev`**: Starts both Vite dev server (with hot reload) and CLI server simultaneously
132
- - **`pnpm run start <commit>`**: Builds everything and starts production server (for testing final build)
146
+ - **`pnpm run start <commit-ish>`**: Builds everything and starts production server (for testing final build)
133
147
  - **Development mode**: Uses Vite's dev server for hot reload and fast development
134
148
  - **Production mode**: Serves built static files (used by npx and production builds)
135
149
 
136
150
  ## 🏗️ Architecture
137
151
 
138
- - **CLI**: Commander.js for argument parsing
152
+ - **CLI**: Commander.js for argument parsing with comprehensive validation
139
153
  - **Backend**: Express server with simple-git for diff processing
140
154
  - **Frontend**: React 18 + TypeScript + Vite
141
155
  - **Styling**: Tailwind CSS v4 with GitHub-like dark theme
142
156
  - **Syntax Highlighting**: Prism.js with dynamic language loading
143
- - **Testing**: Vitest for unit tests
157
+ - **Testing**: Vitest for unit tests with co-located test files
144
158
  - **Quality**: ESLint, Prettier, lefthook pre-commit hooks
145
159
 
160
+ ### Key Components
161
+
162
+ - **Validation System**: Unified validation logic for CLI arguments with comprehensive error handling
163
+ - **Dual Parameter System**: Internal refactoring splits commitish into targetCommitish and baseCommitish for flexibility
164
+ - **Special Argument Support**: Working directory, staging area, and uncommitted changes detection
165
+ - **Hash Utilities**: Consistent short hash generation for commit display
166
+
146
167
  ## 📋 Requirements
147
168
 
148
169
  - Node.js ≥ 18.0.0
package/dist/cli/index.js CHANGED
@@ -1,31 +1,68 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
+ import React from 'react';
3
4
  import pkg from '../../package.json' with { type: 'json' };
4
5
  import { startServer } from '../server/server.js';
5
- import { validateCommitish } from './utils.js';
6
+ import { validateDiffArguments } from './utils.js';
7
+ function isSpecialArg(arg) {
8
+ return arg === 'working' || arg === 'staged' || arg === '.';
9
+ }
6
10
  const program = new Command();
7
11
  program
8
12
  .name('reviewit')
9
13
  .description('A lightweight Git diff viewer with GitHub-like interface')
10
14
  .version(pkg.version)
11
- .argument('[commit-ish]', 'Git commit, tag, branch, or HEAD~n reference (default: HEAD)', 'HEAD')
15
+ .argument('[commit-ish]', 'Git commit, tag, branch, HEAD~n reference, or "working"/"staged"/"." (default: HEAD)', 'HEAD')
16
+ .argument('[compare-with]', 'Optional: Compare with this commit/branch (shows diff between commit-ish and compare-with)')
12
17
  .option('--port <port>', 'preferred port (auto-assigned if occupied)', parseInt)
13
18
  .option('--no-open', 'do not automatically open browser')
14
19
  .option('--mode <mode>', 'diff mode (inline only for now)', 'inline')
15
- .action(async (commitish, options) => {
20
+ .option('--tui', 'use terminal UI instead of web interface')
21
+ .action(async (commitish, compareWith, options) => {
16
22
  try {
17
- if (!validateCommitish(commitish)) {
18
- console.error('Error: Invalid commit-ish format');
23
+ // Determine target and base commitish
24
+ let targetCommitish = commitish;
25
+ let baseCommitish;
26
+ if (compareWith) {
27
+ // If compareWith is provided, use it as base
28
+ baseCommitish = compareWith;
29
+ }
30
+ else {
31
+ // Handle special arguments
32
+ if (isSpecialArg(commitish)) {
33
+ baseCommitish = 'HEAD';
34
+ }
35
+ else {
36
+ baseCommitish = commitish + '^';
37
+ }
38
+ }
39
+ if (options.tui) {
40
+ // Check if we're in a TTY environment
41
+ if (!process.stdin.isTTY) {
42
+ console.error('Error: TUI mode requires an interactive terminal (TTY).');
43
+ console.error('Try running the command directly in your terminal without piping.');
44
+ process.exit(1);
45
+ }
46
+ // Dynamic import for TUI mode
47
+ const { render } = await import('ink');
48
+ const { default: TuiApp } = await import('../tui/App.js');
49
+ render(React.createElement(TuiApp, { targetCommitish, baseCommitish }));
50
+ return;
51
+ }
52
+ const validation = validateDiffArguments(targetCommitish, compareWith);
53
+ if (!validation.valid) {
54
+ console.error(`Error: ${validation.error}`);
19
55
  process.exit(1);
20
56
  }
21
57
  const { url } = await startServer({
22
- commitish,
58
+ targetCommitish,
59
+ baseCommitish,
23
60
  preferredPort: options.port,
24
61
  openBrowser: options.open,
25
62
  mode: options.mode,
26
63
  });
27
64
  console.log(`\n🚀 ReviewIt server started on ${url}`);
28
- console.log(`📋 Reviewing: ${commitish}`);
65
+ console.log(`📋 Reviewing: ${targetCommitish}`);
29
66
  if (options.open) {
30
67
  console.log('🌐 Opening browser...\n');
31
68
  }
@@ -1 +1,6 @@
1
1
  export declare function validateCommitish(commitish: string): boolean;
2
+ export declare function shortHash(hash: string): string;
3
+ export declare function validateDiffArguments(targetCommitish: string, baseCommitish?: string): {
4
+ valid: boolean;
5
+ error?: string;
6
+ };
package/dist/cli/utils.js CHANGED
@@ -10,8 +10,8 @@ export function validateCommitish(commitish) {
10
10
  if (trimmed === 'HEAD~') {
11
11
  return false;
12
12
  }
13
- if (trimmed === '.') {
14
- return true; // Allow '.' for working directory diff
13
+ if (trimmed === '.' || trimmed === 'working' || trimmed === 'staged') {
14
+ return true; // Allow special keywords for working directory and staging area diff
15
15
  }
16
16
  const validPatterns = [
17
17
  /^[a-f0-9]{4,40}$/i, // SHA hashes
@@ -20,3 +20,36 @@ export function validateCommitish(commitish) {
20
20
  ];
21
21
  return validPatterns.some((pattern) => pattern.test(trimmed));
22
22
  }
23
+ export function shortHash(hash) {
24
+ return hash.substring(0, 7);
25
+ }
26
+ export function validateDiffArguments(targetCommitish, baseCommitish) {
27
+ // Validate target commitish format
28
+ if (!validateCommitish(targetCommitish)) {
29
+ return { valid: false, error: 'Invalid target commit-ish format' };
30
+ }
31
+ // Validate base commitish format if provided
32
+ if (baseCommitish !== undefined && !validateCommitish(baseCommitish)) {
33
+ return { valid: false, error: 'Invalid base commit-ish format' };
34
+ }
35
+ // Special arguments are only allowed in target, not base
36
+ const specialArgs = ['working', 'staged', '.'];
37
+ if (baseCommitish && specialArgs.includes(baseCommitish)) {
38
+ return {
39
+ valid: false,
40
+ error: `Special arguments (working, staged, .) are only allowed as target, not base. Got base: ${baseCommitish}`,
41
+ };
42
+ }
43
+ // Cannot compare same values
44
+ if (targetCommitish === baseCommitish) {
45
+ return { valid: false, error: `Cannot compare ${targetCommitish} with itself` };
46
+ }
47
+ // "working" shows unstaged changes and cannot be compared with another commit
48
+ if (targetCommitish === 'working' && baseCommitish) {
49
+ return {
50
+ valid: false,
51
+ error: '"working" shows unstaged changes and cannot be compared with another commit. Use "." instead to compare all uncommitted changes with a specific commit.',
52
+ };
53
+ }
54
+ return { valid: true };
55
+ }
@@ -1,23 +1,11 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { validateCommitish, validateDiffArguments, shortHash, parseGitHubPrUrl } from './utils';
2
+ import { validateCommitish, validateDiffArguments, shortHash } from './utils';
3
3
  describe('CLI Utils', () => {
4
4
  describe('validateCommitish', () => {
5
5
  it('should validate full SHA hashes', () => {
6
6
  expect(validateCommitish('a1b2c3d4e5f6789012345678901234567890abcd')).toBe(true);
7
7
  expect(validateCommitish('abc123')).toBe(true);
8
8
  });
9
- it('should validate SHA hashes with parent references', () => {
10
- expect(validateCommitish('a1b2c3d4e5f6789012345678901234567890abcd^')).toBe(true);
11
- expect(validateCommitish('abc123^')).toBe(true);
12
- expect(validateCommitish('abc123^^')).toBe(true);
13
- expect(validateCommitish('bd4b7513e075b5b245284c38fd23427b9bd0f42e^')).toBe(true);
14
- });
15
- it('should validate SHA hashes with ancestor references', () => {
16
- expect(validateCommitish('a1b2c3d4e5f6789012345678901234567890abcd~1')).toBe(true);
17
- expect(validateCommitish('abc123~5')).toBe(true);
18
- expect(validateCommitish('abc123~10')).toBe(true);
19
- expect(validateCommitish('bd4b7513e075b5b245284c38fd23427b9bd0f42e~2')).toBe(true);
20
- });
21
9
  it('should validate HEAD references', () => {
22
10
  expect(validateCommitish('HEAD')).toBe(true);
23
11
  expect(validateCommitish('HEAD~1')).toBe(true);
@@ -85,12 +73,6 @@ describe('CLI Utils', () => {
85
73
  expect(validateDiffArguments('staged', 'HEAD')).toEqual({ valid: true });
86
74
  expect(validateDiffArguments('.', 'main')).toEqual({ valid: true });
87
75
  });
88
- it('should allow staged as base only with working target', () => {
89
- expect(validateDiffArguments('working', 'staged')).toEqual({ valid: true });
90
- const result = validateDiffArguments('HEAD', 'staged');
91
- expect(result.valid).toBe(false);
92
- expect(result.error).toBe('Special arguments (working, staged, .) are only allowed as target, not base. Got base: staged');
93
- });
94
76
  });
95
77
  describe('same value comparison', () => {
96
78
  it('should reject same target and base values', () => {
@@ -115,17 +97,6 @@ describe('CLI Utils', () => {
115
97
  it('should allow working without compareWith', () => {
116
98
  expect(validateDiffArguments('working')).toEqual({ valid: true });
117
99
  });
118
- it('should allow working with staged', () => {
119
- expect(validateDiffArguments('working', 'staged')).toEqual({ valid: true });
120
- });
121
- it('should reject working with other commits', () => {
122
- const result1 = validateDiffArguments('working', 'main');
123
- expect(result1.valid).toBe(false);
124
- expect(result1.error).toBe('"working" shows unstaged changes and cannot be compared with another commit. Use "." instead to compare all uncommitted changes with a specific commit.');
125
- const result2 = validateDiffArguments('working', 'abc123');
126
- expect(result2.valid).toBe(false);
127
- expect(result2.error).toBe('"working" shows unstaged changes and cannot be compared with another commit. Use "." instead to compare all uncommitted changes with a specific commit.');
128
- });
129
100
  it('should allow other special args with compareWith', () => {
130
101
  expect(validateDiffArguments('staged', 'HEAD')).toEqual({ valid: true });
131
102
  expect(validateDiffArguments('.', 'main')).toEqual({ valid: true });
@@ -143,14 +114,6 @@ describe('CLI Utils', () => {
143
114
  valid: true,
144
115
  });
145
116
  });
146
- it('should handle SHA hashes with parent/ancestor references', () => {
147
- expect(validateDiffArguments('bd4b7513e075b5b245284c38fd23427b9bd0f42e^', 'abc123')).toEqual({ valid: true });
148
- expect(validateDiffArguments('abc123', 'def456^')).toEqual({ valid: true });
149
- expect(validateDiffArguments('abc123~1', 'def456~2')).toEqual({ valid: true });
150
- expect(validateDiffArguments('a1b2c3d4e5f6789012345678901234567890abcd^', 'HEAD')).toEqual({
151
- valid: true,
152
- });
153
- });
154
117
  });
155
118
  });
156
119
  describe('shortHash', () => {
@@ -164,51 +127,4 @@ describe('CLI Utils', () => {
164
127
  expect(shortHash('')).toBe('');
165
128
  });
166
129
  });
167
- describe('parseGitHubPrUrl', () => {
168
- it('should parse valid GitHub PR URLs', () => {
169
- const result = parseGitHubPrUrl('https://github.com/owner/repo/pull/123');
170
- expect(result).toEqual({
171
- owner: 'owner',
172
- repo: 'repo',
173
- pullNumber: 123,
174
- });
175
- });
176
- it('should parse GitHub PR URLs with additional path segments', () => {
177
- const result = parseGitHubPrUrl('https://github.com/owner/repo/pull/456/files');
178
- expect(result).toEqual({
179
- owner: 'owner',
180
- repo: 'repo',
181
- pullNumber: 456,
182
- });
183
- });
184
- it('should parse GitHub PR URLs with query parameters', () => {
185
- const result = parseGitHubPrUrl('https://github.com/owner/repo/pull/789?tab=files');
186
- expect(result).toEqual({
187
- owner: 'owner',
188
- repo: 'repo',
189
- pullNumber: 789,
190
- });
191
- });
192
- it('should handle URLs with hyphens and underscores in owner/repo names', () => {
193
- const result = parseGitHubPrUrl('https://github.com/owner-name/repo_name/pull/123');
194
- expect(result).toEqual({
195
- owner: 'owner-name',
196
- repo: 'repo_name',
197
- pullNumber: 123,
198
- });
199
- });
200
- it('should return null for invalid URLs', () => {
201
- expect(parseGitHubPrUrl('not-a-url')).toBe(null);
202
- expect(parseGitHubPrUrl('https://example.com/owner/repo/pull/123')).toBe(null);
203
- expect(parseGitHubPrUrl('https://github.com/owner/repo/issues/123')).toBe(null);
204
- expect(parseGitHubPrUrl('https://github.com/owner/repo')).toBe(null);
205
- expect(parseGitHubPrUrl('https://github.com/owner/repo/pull/abc')).toBe(null);
206
- });
207
- it('should handle malformed URLs gracefully', () => {
208
- expect(parseGitHubPrUrl('')).toBe(null);
209
- expect(parseGitHubPrUrl('https://github.com')).toBe(null);
210
- expect(parseGitHubPrUrl('https://github.com/owner')).toBe(null);
211
- expect(parseGitHubPrUrl('https://github.com/owner/repo/pull')).toBe(null);
212
- });
213
- });
214
130
  });
@@ -0,0 +1 @@
1
+ /*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */@layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-semibold:600;--radius-md:.375rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-github-bg-primary:#0d1117;--color-github-bg-secondary:#161b22;--color-github-bg-tertiary:#21262d;--color-github-border:#30363d;--color-github-text-primary:#f0f6fc;--color-github-text-secondary:#8b949e;--color-github-text-muted:#6e7681;--color-github-accent:#238636;--color-github-danger:#da3633;--color-github-warning:#d29922;--color-diff-addition-bg:#0d4429;--color-diff-deletion-bg:#67060c}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentColor}::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.static{position:static}.sticky{position:sticky}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.left-3{left:calc(var(--spacing)*3)}.z-10{z-index:10}.m-0{margin:calc(var(--spacing)*0)}.m-2{margin:calc(var(--spacing)*2)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-4{margin-inline:calc(var(--spacing)*4)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.hidden{display:none}.inline{display:inline}.h-4{height:calc(var(--spacing)*4)}.h-full{height:100%}.h-screen{height:100vh}.min-h-\[20px\]{min-height:20px}.min-h-\[60px\]{min-height:60px}.w-1{width:calc(var(--spacing)*1)}.w-1\/2{width:50%}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-\[50px\]{width:50px}.w-\[60px\]{width:60px}.w-full{width:100%}.min-w-0{min-width:calc(var(--spacing)*0)}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-col-resize{cursor:col-resize}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-none{resize:none}.resize-y{resize:vertical}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-none{--tw-border-style:none;border-style:none}.border-\[var\(--border-muted\)\]{border-color:var(--border-muted)}.border-blue-600\/50{border-color:#155dfc80}@supports (color:color-mix(in lab,red,red)){.border-blue-600\/50{border-color:color-mix(in oklab,var(--color-blue-600)50%,transparent)}}.border-github-accent{border-color:var(--color-github-accent)}.border-github-border{border-color:var(--color-github-border)}.border-github-text-muted{border-color:var(--color-github-text-muted)}.border-gray-500{border-color:var(--color-gray-500)}.border-gray-600\/50{border-color:#4a556580}@supports (color:color-mix(in lab,red,red)){.border-gray-600\/50{border-color:color-mix(in oklab,var(--color-gray-600)50%,transparent)}}.border-green-600\/50{border-color:#00a54480}@supports (color:color-mix(in lab,red,red)){.border-green-600\/50{border-color:color-mix(in oklab,var(--color-green-600)50%,transparent)}}.border-red-600\/50{border-color:#e4001480}@supports (color:color-mix(in lab,red,red)){.border-red-600\/50{border-color:color-mix(in oklab,var(--color-red-600)50%,transparent)}}.border-yellow-400\/30{border-color:#fac8004d}@supports (color:color-mix(in lab,red,red)){.border-yellow-400\/30{border-color:color-mix(in oklab,var(--color-yellow-400)30%,transparent)}}.border-yellow-600\/50{border-color:#cd890080}@supports (color:color-mix(in lab,red,red)){.border-yellow-600\/50{border-color:color-mix(in oklab,var(--color-yellow-600)50%,transparent)}}.border-l-yellow-400{border-left-color:var(--color-yellow-400)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-blue-700\/40{background-color:#1447e666}@supports (color:color-mix(in lab,red,red)){.bg-blue-700\/40{background-color:color-mix(in oklab,var(--color-blue-700)40%,transparent)}}.bg-diff-addition-bg{background-color:var(--color-diff-addition-bg)}.bg-diff-deletion-bg{background-color:var(--color-diff-deletion-bg)}.bg-github-accent{background-color:var(--color-github-accent)}.bg-github-bg-primary{background-color:var(--color-github-bg-primary)}.bg-github-bg-secondary{background-color:var(--color-github-bg-secondary)}.bg-github-bg-tertiary{background-color:var(--color-github-bg-tertiary)}.bg-github-border{background-color:var(--color-github-border)}.bg-github-warning\/20{background-color:#d2992233}@supports (color:color-mix(in lab,red,red)){.bg-github-warning\/20{background-color:color-mix(in oklab,var(--color-github-warning)20%,transparent)}}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700\/40{background-color:#36415366}@supports (color:color-mix(in lab,red,red)){.bg-gray-700\/40{background-color:color-mix(in oklab,var(--color-gray-700)40%,transparent)}}.bg-green-100\/10{background-color:#dcfce71a}@supports (color:color-mix(in lab,red,red)){.bg-green-100\/10{background-color:color-mix(in oklab,var(--color-green-100)10%,transparent)}}.bg-green-700\/40{background-color:#00813866}@supports (color:color-mix(in lab,red,red)){.bg-green-700\/40{background-color:color-mix(in oklab,var(--color-green-700)40%,transparent)}}.bg-red-100\/10{background-color:#ffe2e21a}@supports (color:color-mix(in lab,red,red)){.bg-red-100\/10{background-color:color-mix(in oklab,var(--color-red-100)10%,transparent)}}.bg-red-700\/40{background-color:#bf000f66}@supports (color:color-mix(in lab,red,red)){.bg-red-700\/40{background-color:color-mix(in oklab,var(--color-red-700)40%,transparent)}}.bg-transparent{background-color:#0000}.bg-yellow-500\/10{background-color:#edb2001a}@supports (color:color-mix(in lab,red,red)){.bg-yellow-500\/10{background-color:color-mix(in oklab,var(--color-yellow-500)10%,transparent)}}.bg-yellow-700\/40{background-color:#a3610066}@supports (color:color-mix(in lab,red,red)){.bg-yellow-700\/40{background-color:color-mix(in oklab,var(--color-yellow-700)40%,transparent)}}.bg-yellow-800\/30{background-color:#874b004d}@supports (color:color-mix(in lab,red,red)){.bg-yellow-800\/30{background-color:color-mix(in oklab,var(--color-yellow-800)30%,transparent)}}.bg-yellow-900\/20{background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.bg-yellow-900\/20{background-color:color-mix(in oklab,var(--color-yellow-900)20%,transparent)}}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-3{padding:calc(var(--spacing)*3)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.pr-3{padding-right:calc(var(--spacing)*3)}.pl-9{padding-left:calc(var(--spacing)*9)}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-5{--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5)}.leading-6{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-all{word-break:break-all}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-200{color:var(--color-blue-200)}.text-github-accent{color:var(--color-github-accent)}.text-github-danger{color:var(--color-github-danger)}.text-github-text-muted{color:var(--color-github-text-muted)}.text-github-text-primary{color:var(--color-github-text-primary)}.text-github-text-secondary{color:var(--color-github-text-secondary)}.text-github-warning{color:var(--color-github-warning)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-green-200{color:var(--color-green-200)}.text-red-200{color:var(--color-red-200)}.text-white{color:var(--color-white)}.text-yellow-100{color:var(--color-yellow-100)}.text-yellow-200{color:var(--color-yellow-200)}.italic{font-style:italic}.line-through{text-decoration-line:line-through}.placeholder-github-text-muted::-moz-placeholder{color:var(--color-github-text-muted)}.placeholder-github-text-muted::placeholder{color:var(--color-github-text-muted)}.opacity-70{opacity:.7}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}@media (hover:hover){.hover\:border-blue-500:hover{border-color:var(--color-blue-500)}.hover\:border-github-accent\/50:hover{border-color:#23863680}@supports (color:color-mix(in lab,red,red)){.hover\:border-github-accent\/50:hover{border-color:color-mix(in oklab,var(--color-github-accent)50%,transparent)}}.hover\:border-gray-500:hover{border-color:var(--color-gray-500)}.hover\:border-green-500:hover{border-color:var(--color-green-500)}.hover\:border-red-500:hover{border-color:var(--color-red-500)}.hover\:border-yellow-500:hover{border-color:var(--color-yellow-500)}.hover\:bg-blue-600\/50:hover{background-color:#155dfc80}@supports (color:color-mix(in lab,red,red)){.hover\:bg-blue-600\/50:hover{background-color:color-mix(in oklab,var(--color-blue-600)50%,transparent)}}.hover\:bg-github-bg-tertiary:hover{background-color:var(--color-github-bg-tertiary)}.hover\:bg-github-text-muted:hover{background-color:var(--color-github-text-muted)}.hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.hover\:bg-gray-600\/50:hover{background-color:#4a556580}@supports (color:color-mix(in lab,red,red)){.hover\:bg-gray-600\/50:hover{background-color:color-mix(in oklab,var(--color-gray-600)50%,transparent)}}.hover\:bg-green-600\/50:hover{background-color:#00a54480}@supports (color:color-mix(in lab,red,red)){.hover\:bg-green-600\/50:hover{background-color:color-mix(in oklab,var(--color-green-600)50%,transparent)}}.hover\:bg-red-600\/50:hover{background-color:#e4001480}@supports (color:color-mix(in lab,red,red)){.hover\:bg-red-600\/50:hover{background-color:color-mix(in oklab,var(--color-red-600)50%,transparent)}}.hover\:bg-yellow-600\/50:hover{background-color:#cd890080}@supports (color:color-mix(in lab,red,red)){.hover\:bg-yellow-600\/50:hover{background-color:color-mix(in oklab,var(--color-yellow-600)50%,transparent)}}.hover\:bg-yellow-800\/30:hover{background-color:#874b004d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-yellow-800\/30:hover{background-color:color-mix(in oklab,var(--color-yellow-800)30%,transparent)}}.hover\:text-github-text-primary:hover{color:var(--color-github-text-primary)}.hover\:text-white:hover{color:var(--color-white)}.hover\:opacity-80:hover{opacity:.8}}.focus\:min-h-\[80px\]:focus{min-height:80px}.focus\:border-blue-600:focus{border-color:var(--color-blue-600)}.focus\:border-github-accent:focus{border-color:var(--color-github-accent)}.focus\:border-yellow-500:focus{border-color:var(--color-yellow-500)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-600\/30:focus{--tw-ring-color:#155dfc4d}@supports (color:color-mix(in lab,red,red)){.focus\:ring-blue-600\/30:focus{--tw-ring-color:color-mix(in oklab,var(--color-blue-600)30%,transparent)}}.focus\:ring-yellow-500:focus{--tw-ring-color:var(--color-yellow-500)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:opacity-50:disabled{opacity:.5}.\[\&_code\]\:\!bg-transparent code{background-color:#0000!important}.\[\&_code\]\:text-inherit code{color:inherit}.\[\&_pre\]\:m-0 pre{margin:calc(var(--spacing)*0)}.\[\&_pre\]\:\!bg-transparent pre{background-color:#0000!important}.\[\&_pre\]\:p-0 pre{padding:calc(var(--spacing)*0)}.\[\&_pre\]\:text-inherit pre{color:inherit}}html,body{color:#f0f6fc;background-color:#0d1117;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Noto Sans,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5}button{cursor:pointer}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}
@@ -10,8 +10,8 @@
10
10
  <link rel="icon" type="image/png" sizes="512x512" href="/icon-512x512.png" />
11
11
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
12
12
  <title>ReviewIt - Git Diff Viewer</title>
13
- <script type="module" crossorigin src="/assets/index-DpDAJan6.js"></script>
14
- <link rel="stylesheet" crossorigin href="/assets/index-CpclbaYk.css">
13
+ <script type="module" crossorigin src="/assets/index-B67TY4_1.js"></script>
14
+ <link rel="stylesheet" crossorigin href="/assets/index-BHrhjAlZ.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -1,5 +1,5 @@
1
1
  import simpleGit from 'simple-git';
2
- import { validateDiffArguments, createCommitRangeString } from '../cli/utils.js';
2
+ import { validateDiffArguments } from '../cli/utils.js';
3
3
  export async function loadGitDiff(targetCommitish, baseCommitish) {
4
4
  // Validate arguments
5
5
  const validation = validateDiffArguments(targetCommitish, baseCommitish);
@@ -23,10 +23,7 @@ export async function loadGitDiff(targetCommitish, baseCommitish) {
23
23
  }
24
24
  else {
25
25
  // Both are regular commits: standard commit-to-commit comparison
26
- diff = await git.diff([
27
- createCommitRangeString(baseCommitish, targetCommitish),
28
- '--name-status',
29
- ]);
26
+ diff = await git.diff([`${baseCommitish}..${targetCommitish}`, '--name-status']);
30
27
  if (!diff.trim()) {
31
28
  // Try without parent (for initial commit)
32
29
  const diffInitial = await git.diff([targetCommitish, '--name-status']);
@@ -63,11 +60,7 @@ export async function loadGitDiff(targetCommitish, baseCommitish) {
63
60
  else {
64
61
  try {
65
62
  // Both are regular commits: standard commit-to-commit comparison
66
- fileDiff = await git.diff([
67
- createCommitRangeString(baseCommitish, targetCommitish),
68
- '--',
69
- path,
70
- ]);
63
+ fileDiff = await git.diff([`${baseCommitish}..${targetCommitish}`, '--', path]);
71
64
  }
72
65
  catch {
73
66
  // For new files or if parent doesn't exist
@@ -2,7 +2,7 @@ import { type DiffResponse } from '../types/diff.js';
2
2
  export declare class GitDiffParser {
3
3
  private git;
4
4
  constructor(repoPath?: string);
5
- parseDiff(commitish: string, ignoreWhitespace?: boolean): Promise<DiffResponse>;
5
+ parseDiff(targetCommitish: string, baseCommitish: string, ignoreWhitespace?: boolean): Promise<DiffResponse>;
6
6
  private parseUnifiedDiff;
7
7
  private parseFileBlock;
8
8
  private parseChunks;
@@ -1,25 +1,42 @@
1
1
  import { simpleGit } from 'simple-git';
2
+ import { validateDiffArguments, shortHash } from '../cli/utils.js';
2
3
  export class GitDiffParser {
3
4
  constructor(repoPath = process.cwd()) {
4
5
  this.git = simpleGit(repoPath);
5
6
  }
6
- async parseDiff(commitish, ignoreWhitespace = false) {
7
+ async parseDiff(targetCommitish, baseCommitish, ignoreWhitespace = false) {
7
8
  try {
9
+ // Validate arguments
10
+ const validation = validateDiffArguments(targetCommitish, baseCommitish);
11
+ if (!validation.valid) {
12
+ throw new Error(validation.error);
13
+ }
8
14
  let resolvedCommit;
9
15
  let diffArgs;
10
- if (commitish === '.') {
11
- // Show diff between HEAD and working directory (uncommitted changes)
12
- resolvedCommit = 'Working Directory (uncommitted changes)';
13
- diffArgs = ['HEAD'];
16
+ // Handle target special chars (base is always a regular commit)
17
+ if (targetCommitish === 'working') {
18
+ // Show unstaged changes (working vs staged)
19
+ resolvedCommit = 'Working Directory (unstaged changes)';
20
+ diffArgs = [];
21
+ }
22
+ else if (targetCommitish === 'staged') {
23
+ // Show staged changes against base commit
24
+ const baseHash = await this.git.revparse([baseCommitish]);
25
+ resolvedCommit = `${shortHash(baseHash)} vs Staging Area (staged changes)`;
26
+ diffArgs = ['--cached', baseCommitish];
27
+ }
28
+ else if (targetCommitish === '.') {
29
+ // Show all uncommitted changes against base commit
30
+ const baseHash = await this.git.revparse([baseCommitish]);
31
+ resolvedCommit = `${shortHash(baseHash)} vs Working Directory (all uncommitted changes)`;
32
+ diffArgs = [baseCommitish];
14
33
  }
15
34
  else {
16
- // Resolve commitish to actual commit hash and get short version
17
- const fullHash = await this.git.revparse([commitish]);
18
- const shortHash = fullHash.substring(0, 7);
19
- const parentHash = await this.git.revparse([`${commitish}^`]);
20
- const shortParentHash = parentHash.substring(0, 7);
21
- resolvedCommit = `${shortParentHash}..${shortHash}`;
22
- diffArgs = [`${commitish}^`, commitish];
35
+ // Both are regular commits: standard commit-to-commit comparison
36
+ const targetHash = await this.git.revparse([targetCommitish]);
37
+ const baseHash = await this.git.revparse([baseCommitish]);
38
+ resolvedCommit = `${shortHash(baseHash)}..${shortHash(targetHash)}`;
39
+ diffArgs = [baseCommitish, targetCommitish];
23
40
  }
24
41
  if (ignoreWhitespace) {
25
42
  diffArgs.push('-w');
@@ -34,7 +51,7 @@ export class GitDiffParser {
34
51
  };
35
52
  }
36
53
  catch (error) {
37
- throw new Error(`Failed to parse diff for ${commitish}: ${error instanceof Error ? error.message : 'Unknown error'}`);
54
+ throw new Error(`Failed to parse diff for ${targetCommitish} vs ${baseCommitish}: ${error instanceof Error ? error.message : 'Unknown error'}`);
38
55
  }
39
56
  }
40
57
  parseUnifiedDiff(diffText, summary) {
@@ -134,8 +151,8 @@ export class GitDiffParser {
134
151
  }
135
152
  async validateCommit(commitish) {
136
153
  try {
137
- if (commitish === '.') {
138
- // For working directory, just check if we're in a git repo
154
+ if (commitish === '.' || commitish === 'working' || commitish === 'staged') {
155
+ // For working directory or staging area, just check if we're in a git repo
139
156
  await this.git.status();
140
157
  return true;
141
158
  }
@@ -1,5 +1,6 @@
1
1
  interface ServerOptions {
2
- commitish: string;
2
+ targetCommitish: string;
3
+ baseCommitish: string;
3
4
  preferredPort?: number;
4
5
  openBrowser?: boolean;
5
6
  mode?: string;
@@ -17,16 +17,16 @@ export async function startServer(options) {
17
17
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
18
18
  next();
19
19
  });
20
- const isValidCommit = await parser.validateCommit(options.commitish);
20
+ const isValidCommit = await parser.validateCommit(options.targetCommitish);
21
21
  if (!isValidCommit) {
22
- throw new Error(`Invalid or non-existent commit: ${options.commitish}`);
22
+ throw new Error(`Invalid or non-existent commit: ${options.targetCommitish}`);
23
23
  }
24
- diffData = await parser.parseDiff(options.commitish, currentIgnoreWhitespace);
24
+ diffData = await parser.parseDiff(options.targetCommitish, options.baseCommitish, currentIgnoreWhitespace);
25
25
  app.get('/api/diff', async (req, res) => {
26
26
  const ignoreWhitespace = req.query.ignoreWhitespace === 'true';
27
27
  if (ignoreWhitespace !== currentIgnoreWhitespace) {
28
28
  currentIgnoreWhitespace = ignoreWhitespace;
29
- diffData = await parser.parseDiff(options.commitish, ignoreWhitespace);
29
+ diffData = await parser.parseDiff(options.targetCommitish, options.baseCommitish, ignoreWhitespace);
30
30
  }
31
31
  res.json({ ...diffData, ignoreWhitespace });
32
32
  });
package/dist/tui/App.d.ts CHANGED
@@ -2,7 +2,6 @@ import React from 'react';
2
2
  interface AppProps {
3
3
  targetCommitish: string;
4
4
  baseCommitish: string;
5
- mode?: string;
6
5
  }
7
6
  declare const App: React.FC<AppProps>;
8
7
  export default App;
package/dist/tui/App.js CHANGED
@@ -5,12 +5,12 @@ import FileList from './components/FileList.js';
5
5
  import DiffViewer from './components/DiffViewer.js';
6
6
  import SideBySideDiffViewer from './components/SideBySideDiffViewer.js';
7
7
  import StatusBar from './components/StatusBar.js';
8
- const App = ({ targetCommitish, baseCommitish, mode }) => {
8
+ const App = ({ targetCommitish, baseCommitish }) => {
9
9
  const [files, setFiles] = useState([]);
10
10
  const [selectedFileIndex, setSelectedFileIndex] = useState(0);
11
11
  const [loading, setLoading] = useState(true);
12
12
  const [error, setError] = useState(null);
13
- const [viewMode, setViewMode] = useState(mode === 'inline' ? 'inline' : 'side-by-side');
13
+ const [viewMode, setViewMode] = useState('side-by-side');
14
14
  const { exit } = useApp();
15
15
  const loadDiff = async () => {
16
16
  setLoading(true);
@@ -49,7 +49,7 @@ const App = ({ targetCommitish, baseCommitish, mode }) => {
49
49
  setViewMode('side-by-side');
50
50
  }
51
51
  if (input === 'd') {
52
- setViewMode('inline');
52
+ setViewMode('diff');
53
53
  }
54
54
  }
55
55
  else {
@@ -84,7 +84,7 @@ const App = ({ targetCommitish, baseCommitish, mode }) => {
84
84
  React.createElement(Box, { flexGrow: 1, flexDirection: "column" }, viewMode === 'list' ? (React.createElement(FileList, { files: files, selectedIndex: selectedFileIndex })) : viewMode === 'side-by-side' ? (React.createElement(SideBySideDiffViewer, { files: files, initialFileIndex: selectedFileIndex, onBack: () => setViewMode('list') })) : (React.createElement(DiffViewer, { files: files, initialFileIndex: selectedFileIndex, onBack: () => setViewMode('list') }))),
85
85
  React.createElement(Box, { borderStyle: "single", paddingX: 1 },
86
86
  React.createElement(Text, { dimColor: true }, viewMode === 'list'
87
- ? '↑/↓ or j/k: navigate | Enter/Space: side-by-side | d: inline diff | r: reload | q: quit'
87
+ ? '↑/↓ or j/k: navigate | Enter/Space: side-by-side | d: unified diff | r: reload | q: quit'
88
88
  : viewMode === 'side-by-side'
89
89
  ? 'Tab: next file | Shift+Tab: prev | ↑/↓ or j/k: scroll | ESC/b: list | r: reload | q: quit'
90
90
  : 'Tab: next | Shift+Tab: prev | ↑/↓ or j/k: scroll | ESC/b: list | r: reload | q: quit'))));
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  interface StatusBarProps {
3
3
  commitish: string;
4
4
  totalFiles: number;
5
- currentMode: 'list' | 'inline' | 'side-by-side';
5
+ currentMode: 'list' | 'diff' | 'side-by-side';
6
6
  }
7
7
  declare const StatusBar: React.FC<StatusBarProps>;
8
8
  export default StatusBar;
@@ -17,7 +17,7 @@ const StatusBar = ({ commitish, totalFiles, currentMode }) => {
17
17
  ? 'File List'
18
18
  : currentMode === 'side-by-side'
19
19
  ? 'Side-by-Side'
20
- : 'Inline Diff',
20
+ : 'Unified Diff',
21
21
  "]"))));
22
22
  };
23
23
  export default StatusBar;
@@ -6,6 +6,13 @@ export interface DiffFile {
6
6
  deletions: number;
7
7
  chunks: DiffChunk[];
8
8
  }
9
+ export interface FileDiff {
10
+ path: string;
11
+ status: 'A' | 'M' | 'D';
12
+ diff: string;
13
+ additions: number;
14
+ deletions: number;
15
+ }
9
16
  export interface DiffChunk {
10
17
  header: string;
11
18
  oldStart: number;
@@ -15,11 +22,14 @@ export interface DiffChunk {
15
22
  lines: DiffLine[];
16
23
  }
17
24
  export interface DiffLine {
18
- type: 'add' | 'delete' | 'normal' | 'hunk';
25
+ type: 'add' | 'delete' | 'normal' | 'hunk' | 'remove' | 'context' | 'header';
19
26
  content: string;
20
27
  oldLineNumber?: number;
21
28
  newLineNumber?: number;
22
29
  }
30
+ export interface ParsedDiff {
31
+ chunks: DiffChunk[];
32
+ }
23
33
  export interface DiffResponse {
24
34
  commit: string;
25
35
  files: DiffFile[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "difit",
3
- "version": "1.0.8",
3
+ "version": "1.1.1",
4
4
  "description": "A lightweight command-line tool that spins up a local web server to display Git commit diffs in a GitHub-like Files changed view",
5
5
  "type": "module",
6
6
  "engines": {
@@ -21,21 +21,22 @@
21
21
  "scripts": {
22
22
  "dev": "node scripts/dev.js",
23
23
  "dev:cli": "tsc --project tsconfig.cli.json && NODE_ENV=development node dist/cli/index.js",
24
- "build": "tsc && vite build",
24
+ "build": "tsc -b && vite build",
25
25
  "build:cli": "tsc --project tsconfig.cli.json",
26
- "start": "pnpm run build && pnpm run build:cli && node dist/cli/index.js",
26
+ "start": "pnpm run build && node dist/cli/index.js",
27
27
  "lint": "eslint .",
28
28
  "lint:fix": "eslint . --fix",
29
29
  "format": "prettier --write .",
30
- "typecheck": "tsc --noEmit",
30
+ "typecheck": "tsc -b",
31
31
  "test": "vitest run",
32
32
  "test:watch": "vitest",
33
33
  "prepare": "lefthook install",
34
- "prepublishOnly": "NODE_ENV=production pnpm run build && pnpm run build:cli"
34
+ "prepublishOnly": "NODE_ENV=production pnpm run build"
35
35
  },
36
36
  "dependencies": {
37
37
  "commander": "^11.1.0",
38
38
  "express": "^4.18.2",
39
+ "ink": "^5.2.1",
39
40
  "lucide-react": "^0.525.0",
40
41
  "open": "^10.0.3",
41
42
  "prism-react-renderer": "^2.4.1",
@@ -1 +0,0 @@
1
- /*! tailwindcss v4.1.11 | MIT License | https://tailwindcss.com */@layer properties{@supports ((-webkit-hyphens:none) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-medium:500;--font-weight-semibold:600;--radius-md:.375rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-github-bg-primary:#0d1117;--color-github-bg-secondary:#161b22;--color-github-bg-tertiary:#21262d;--color-github-border:#30363d;--color-github-text-primary:#f0f6fc;--color-github-text-secondary:#8b949e;--color-github-text-muted:#6e7681;--color-github-accent:#238636;--color-github-danger:#da3633;--color-github-warning:#d29922;--color-diff-addition-bg:#0d4429;--color-diff-deletion-bg:#67060c}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentColor}::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.absolute{position:absolute}.relative{position:relative}.static{position:static}.sticky{position:sticky}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.left-3{left:calc(var(--spacing)*3)}.z-10{z-index:10}.m-0{margin:calc(var(--spacing)*0)}.m-2{margin:calc(var(--spacing)*2)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-4{margin-inline:calc(var(--spacing)*4)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.inline{display:inline}.h-4{height:calc(var(--spacing)*4)}.h-full{height:100%}.h-screen{height:100vh}.min-h-\[20px\]{min-height:20px}.min-h-\[60px\]{min-height:60px}.w-1{width:calc(var(--spacing)*1)}.w-1\/2{width:50%}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-\[50px\]{width:50px}.w-\[60px\]{width:60px}.w-full{width:100%}.min-w-0{min-width:calc(var(--spacing)*0)}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.cursor-col-resize{cursor:col-resize}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-none{resize:none}.resize-y{resize:vertical}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-none{--tw-border-style:none;border-style:none}.border-\[var\(--border-muted\)\]{border-color:var(--border-muted)}.border-blue-600\/50{border-color:#155dfc80}@supports (color:color-mix(in lab,red,red)){.border-blue-600\/50{border-color:color-mix(in oklab,var(--color-blue-600)50%,transparent)}}.border-github-accent{border-color:var(--color-github-accent)}.border-github-border{border-color:var(--color-github-border)}.border-github-text-muted{border-color:var(--color-github-text-muted)}.border-gray-500{border-color:var(--color-gray-500)}.border-gray-600\/50{border-color:#4a556580}@supports (color:color-mix(in lab,red,red)){.border-gray-600\/50{border-color:color-mix(in oklab,var(--color-gray-600)50%,transparent)}}.border-green-600\/50{border-color:#00a54480}@supports (color:color-mix(in lab,red,red)){.border-green-600\/50{border-color:color-mix(in oklab,var(--color-green-600)50%,transparent)}}.border-red-600\/50{border-color:#e4001480}@supports (color:color-mix(in lab,red,red)){.border-red-600\/50{border-color:color-mix(in oklab,var(--color-red-600)50%,transparent)}}.border-yellow-400\/30{border-color:#fac8004d}@supports (color:color-mix(in lab,red,red)){.border-yellow-400\/30{border-color:color-mix(in oklab,var(--color-yellow-400)30%,transparent)}}.border-yellow-600\/50{border-color:#cd890080}@supports (color:color-mix(in lab,red,red)){.border-yellow-600\/50{border-color:color-mix(in oklab,var(--color-yellow-600)50%,transparent)}}.border-l-yellow-400{border-left-color:var(--color-yellow-400)}.bg-\[var\(--bg-secondary\)\]{background-color:var(--bg-secondary)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-blue-700\/40{background-color:#1447e666}@supports (color:color-mix(in lab,red,red)){.bg-blue-700\/40{background-color:color-mix(in oklab,var(--color-blue-700)40%,transparent)}}.bg-diff-addition-bg{background-color:var(--color-diff-addition-bg)}.bg-diff-deletion-bg{background-color:var(--color-diff-deletion-bg)}.bg-github-accent{background-color:var(--color-github-accent)}.bg-github-bg-primary{background-color:var(--color-github-bg-primary)}.bg-github-bg-secondary{background-color:var(--color-github-bg-secondary)}.bg-github-bg-tertiary{background-color:var(--color-github-bg-tertiary)}.bg-github-border{background-color:var(--color-github-border)}.bg-github-warning\/20{background-color:#d2992233}@supports (color:color-mix(in lab,red,red)){.bg-github-warning\/20{background-color:color-mix(in oklab,var(--color-github-warning)20%,transparent)}}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700\/40{background-color:#36415366}@supports (color:color-mix(in lab,red,red)){.bg-gray-700\/40{background-color:color-mix(in oklab,var(--color-gray-700)40%,transparent)}}.bg-green-100\/10{background-color:#dcfce71a}@supports (color:color-mix(in lab,red,red)){.bg-green-100\/10{background-color:color-mix(in oklab,var(--color-green-100)10%,transparent)}}.bg-green-700\/40{background-color:#00813866}@supports (color:color-mix(in lab,red,red)){.bg-green-700\/40{background-color:color-mix(in oklab,var(--color-green-700)40%,transparent)}}.bg-red-100\/10{background-color:#ffe2e21a}@supports (color:color-mix(in lab,red,red)){.bg-red-100\/10{background-color:color-mix(in oklab,var(--color-red-100)10%,transparent)}}.bg-red-700\/40{background-color:#bf000f66}@supports (color:color-mix(in lab,red,red)){.bg-red-700\/40{background-color:color-mix(in oklab,var(--color-red-700)40%,transparent)}}.bg-transparent{background-color:#0000}.bg-yellow-500\/10{background-color:#edb2001a}@supports (color:color-mix(in lab,red,red)){.bg-yellow-500\/10{background-color:color-mix(in oklab,var(--color-yellow-500)10%,transparent)}}.bg-yellow-700\/40{background-color:#a3610066}@supports (color:color-mix(in lab,red,red)){.bg-yellow-700\/40{background-color:color-mix(in oklab,var(--color-yellow-700)40%,transparent)}}.bg-yellow-800\/30{background-color:#874b004d}@supports (color:color-mix(in lab,red,red)){.bg-yellow-800\/30{background-color:color-mix(in oklab,var(--color-yellow-800)30%,transparent)}}.bg-yellow-900\/20{background-color:#733e0a33}@supports (color:color-mix(in lab,red,red)){.bg-yellow-900\/20{background-color:color-mix(in oklab,var(--color-yellow-900)20%,transparent)}}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-3{padding:calc(var(--spacing)*3)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.pr-3{padding-right:calc(var(--spacing)*3)}.pl-9{padding-left:calc(var(--spacing)*9)}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-5{--tw-leading:calc(var(--spacing)*5);line-height:calc(var(--spacing)*5)}.leading-6{--tw-leading:calc(var(--spacing)*6);line-height:calc(var(--spacing)*6)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.break-all{word-break:break-all}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-200{color:var(--color-blue-200)}.text-github-accent{color:var(--color-github-accent)}.text-github-danger{color:var(--color-github-danger)}.text-github-text-muted{color:var(--color-github-text-muted)}.text-github-text-primary{color:var(--color-github-text-primary)}.text-github-text-secondary{color:var(--color-github-text-secondary)}.text-github-warning{color:var(--color-github-warning)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-green-200{color:var(--color-green-200)}.text-red-200{color:var(--color-red-200)}.text-white{color:var(--color-white)}.text-yellow-100{color:var(--color-yellow-100)}.text-yellow-200{color:var(--color-yellow-200)}.italic{font-style:italic}.line-through{text-decoration-line:line-through}.placeholder-github-text-muted::-moz-placeholder{color:var(--color-github-text-muted)}.placeholder-github-text-muted::placeholder{color:var(--color-github-text-muted)}.opacity-70{opacity:.7}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}@media (hover:hover){.hover\:border-blue-500:hover{border-color:var(--color-blue-500)}.hover\:border-github-accent\/50:hover{border-color:#23863680}@supports (color:color-mix(in lab,red,red)){.hover\:border-github-accent\/50:hover{border-color:color-mix(in oklab,var(--color-github-accent)50%,transparent)}}.hover\:border-gray-500:hover{border-color:var(--color-gray-500)}.hover\:border-green-500:hover{border-color:var(--color-green-500)}.hover\:border-red-500:hover{border-color:var(--color-red-500)}.hover\:border-yellow-500:hover{border-color:var(--color-yellow-500)}.hover\:bg-blue-600\/50:hover{background-color:#155dfc80}@supports (color:color-mix(in lab,red,red)){.hover\:bg-blue-600\/50:hover{background-color:color-mix(in oklab,var(--color-blue-600)50%,transparent)}}.hover\:bg-github-bg-tertiary:hover{background-color:var(--color-github-bg-tertiary)}.hover\:bg-github-text-muted:hover{background-color:var(--color-github-text-muted)}.hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.hover\:bg-gray-600\/50:hover{background-color:#4a556580}@supports (color:color-mix(in lab,red,red)){.hover\:bg-gray-600\/50:hover{background-color:color-mix(in oklab,var(--color-gray-600)50%,transparent)}}.hover\:bg-green-600\/50:hover{background-color:#00a54480}@supports (color:color-mix(in lab,red,red)){.hover\:bg-green-600\/50:hover{background-color:color-mix(in oklab,var(--color-green-600)50%,transparent)}}.hover\:bg-red-600\/50:hover{background-color:#e4001480}@supports (color:color-mix(in lab,red,red)){.hover\:bg-red-600\/50:hover{background-color:color-mix(in oklab,var(--color-red-600)50%,transparent)}}.hover\:bg-yellow-600\/50:hover{background-color:#cd890080}@supports (color:color-mix(in lab,red,red)){.hover\:bg-yellow-600\/50:hover{background-color:color-mix(in oklab,var(--color-yellow-600)50%,transparent)}}.hover\:bg-yellow-800\/30:hover{background-color:#874b004d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-yellow-800\/30:hover{background-color:color-mix(in oklab,var(--color-yellow-800)30%,transparent)}}.hover\:text-github-text-primary:hover{color:var(--color-github-text-primary)}.hover\:text-white:hover{color:var(--color-white)}.hover\:opacity-80:hover{opacity:.8}}.focus\:min-h-\[80px\]:focus{min-height:80px}.focus\:border-blue-600:focus{border-color:var(--color-blue-600)}.focus\:border-github-accent:focus{border-color:var(--color-github-accent)}.focus\:border-yellow-500:focus{border-color:var(--color-yellow-500)}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-600\/30:focus{--tw-ring-color:#155dfc4d}@supports (color:color-mix(in lab,red,red)){.focus\:ring-blue-600\/30:focus{--tw-ring-color:color-mix(in oklab,var(--color-blue-600)30%,transparent)}}.focus\:ring-yellow-500:focus{--tw-ring-color:var(--color-yellow-500)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:opacity-50:disabled{opacity:.5}.\[\&_code\]\:\!bg-transparent code{background-color:#0000!important}.\[\&_code\]\:text-inherit code{color:inherit}.\[\&_pre\]\:m-0 pre{margin:calc(var(--spacing)*0)}.\[\&_pre\]\:\!bg-transparent pre{background-color:#0000!important}.\[\&_pre\]\:p-0 pre{padding:calc(var(--spacing)*0)}.\[\&_pre\]\:text-inherit pre{color:inherit}}html,body{color:#f0f6fc;background-color:#0d1117;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Noto Sans,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5}button{cursor:pointer}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}