critique 0.1.75 → 0.1.78

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/CHANGELOG.md CHANGED
@@ -1,3 +1,47 @@
1
+ # 0.1.78
2
+
3
+ - **Breaking change:** Single positional argument now uses `git diff` instead of `git show`
4
+ - Aligns with `git diff` behavior: `critique <ref>` compares ref to working tree
5
+ - Before: `critique HEAD~1` showed what HEAD~1 commit introduced (just that commit)
6
+ - After: `critique HEAD~1` shows all changes since HEAD~1 (like `git diff HEAD~1`)
7
+ - Examples with `critique HEAD~N`:
8
+ ```
9
+ history: ...---A---B---C <- HEAD
10
+ \
11
+ + uncommitted (if any)
12
+
13
+ critique HEAD~2 = shows B + C + uncommitted
14
+ critique HEAD~1 = shows C + uncommitted
15
+ critique HEAD = shows only uncommitted (empty if none)
16
+ ```
17
+ - Examples with `critique <branch>` (when on feature branch):
18
+ ```
19
+ main: A---B---C
20
+ \
21
+ feature: D---E---F <- HEAD
22
+ \
23
+ + uncommitted (if any)
24
+
25
+ critique main = shows D + E + F + uncommitted
26
+ ```
27
+ - To get the previous behavior (view a specific commit only), use `--commit`:
28
+ ```
29
+ critique --commit HEAD~1 = shows only what HEAD~1 introduced (just that commit)
30
+ critique --commit abc123 = shows only what abc123 introduced
31
+ ```
32
+ - Two positional arguments unchanged: `critique main feature` still uses three-dot syntax
33
+
34
+ # 0.1.77
35
+
36
+ - Web previews:
37
+ - Add favicon support with automatic light/dark mode switching
38
+ - Dark mode shows white diff icon, light mode shows black diff icon
39
+
40
+ # 0.1.76
41
+
42
+ - Syntax highlighting:
43
+ - Add tree-sitter parsers for 21 additional languages: Python, Rust, Go, C++, C#, Bash, C, Java, Ruby, PHP, Scala, HTML, JSON, YAML, Haskell, CSS, Julia, OCaml, Clojure, Swift, Nix
44
+
1
45
  # 0.1.75
2
46
 
3
47
  - Web previews:
@@ -53,7 +97,7 @@
53
97
 
54
98
  - Worker:
55
99
  - Email the license command via Resend on successful checkout
56
- - Success page shows the `npx ciritque login <key>` command
100
+ - Success page shows the `npx critique login <key>` command
57
101
 
58
102
  # 0.1.60
59
103
 
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tommy D. Rossi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -27,14 +27,16 @@ critique
27
27
  # View staged changes
28
28
  critique --staged
29
29
 
30
- # View the last commit (works whether pushed or unpushed)
31
- critique HEAD
30
+ # View changes since a ref (like git diff)
31
+ critique HEAD~1 # shows last 1 commit (changes since HEAD~1)
32
+ critique HEAD~3 # shows last 3 commits
33
+ critique main # shows changes since main (your branch's additions)
32
34
 
33
- # View a specific commit
35
+ # View a specific commit only (what that commit introduced)
34
36
  critique --commit HEAD~1
35
- critique abc1234
37
+ critique --commit abc1234
36
38
 
37
- # View combined changes from last N commits
39
+ # Compare two refs (PR-style, shows what head added since diverging from base)
38
40
  critique HEAD~3 HEAD # shows all changes from 3 commits ago to now
39
41
 
40
42
  # Compare two branches (PR-style, shows what head added since diverging from base)
@@ -109,10 +111,11 @@ critique review --agent claude
109
111
  # Review staged changes
110
112
  critique review --staged
111
113
 
112
- # Review the last commit
113
- critique review HEAD
114
+ # Review changes since a ref (like git diff)
115
+ critique review HEAD~1 # review last 1 commit
116
+ critique review main # review changes since main
114
117
 
115
- # Review a specific commit
118
+ # Review a specific commit only (what that commit introduced)
116
119
  critique review --commit HEAD~1
117
120
  critique review --commit abc1234
118
121
 
@@ -169,10 +172,11 @@ critique web
169
172
  # View staged changes
170
173
  critique web --staged
171
174
 
172
- # View the last commit
173
- critique web HEAD
175
+ # View changes since a ref (like git diff)
176
+ critique web HEAD~1 # last 1 commit
177
+ critique web main # changes since main
174
178
 
175
- # View a specific commit
179
+ # View a specific commit only (what that commit introduced)
176
180
  critique web --commit HEAD~1
177
181
 
178
182
  # Compare branches (PR-style diff)
Binary file
package/package.json CHANGED
@@ -2,7 +2,8 @@
2
2
  "name": "critique",
3
3
  "module": "src/diff.tsx",
4
4
  "type": "module",
5
- "version": "0.1.75",
5
+ "version": "0.1.78",
6
+ "license": "MIT",
6
7
  "private": false,
7
8
  "bin": "./src/cli.tsx",
8
9
  "scripts": {
@@ -19,6 +20,7 @@
19
20
  "@types/bun": "1.3.5",
20
21
  "@types/js-yaml": "^4.0.9",
21
22
  "hono": "^4.7.10",
23
+ "sharp": "^0.34.5",
22
24
  "stripe": "^20.2.0",
23
25
  "typescript": "^5.9.3",
24
26
  "wrangler": "^4.19.1"
@@ -0,0 +1,254 @@
1
+ // Tree-sitter parser configuration for syntax highlighting
2
+ // NOTE: For markdown, javascript and typescript, we use the opentui built-in parsers
3
+ // Warn: when taking queries from the nvim-treesitter repo, make sure to include the query dependencies as well
4
+ // marked with for example `; inherits: ecma` at the top of the file. Just put the dependencies before the actual query.
5
+ // ALSO: Some queries use breaking changes in the nvim-treesitter repo, that are not compatible with the (web-)tree-sitter parser.
6
+ export default {
7
+ parsers: [
8
+ {
9
+ filetype: "python",
10
+ wasm: "https://github.com/tree-sitter/tree-sitter-python/releases/download/v0.23.6/tree-sitter-python.wasm",
11
+ queries: {
12
+ highlights: [
13
+ // NOTE: This nvim-treesitter query is currently broken, because the parser is not compatible with the query apparently.
14
+ // it is using "except" nodes that the parser is complaining about, but it has been in the query for 3+ years.
15
+ // Unclear.
16
+ // "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/python/highlights.scm",
17
+ "https://github.com/tree-sitter/tree-sitter-python/raw/refs/heads/master/queries/highlights.scm",
18
+ ],
19
+ locals: [
20
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/python/locals.scm",
21
+ ],
22
+ },
23
+ },
24
+ {
25
+ filetype: "rust",
26
+ wasm: "https://github.com/tree-sitter/tree-sitter-rust/releases/download/v0.24.0/tree-sitter-rust.wasm",
27
+ queries: {
28
+ highlights: [
29
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/rust/highlights.scm",
30
+ ],
31
+ locals: [
32
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/rust/locals.scm",
33
+ ],
34
+ },
35
+ },
36
+ {
37
+ filetype: "go",
38
+ wasm: "https://github.com/tree-sitter/tree-sitter-go/releases/download/v0.25.0/tree-sitter-go.wasm",
39
+ queries: {
40
+ highlights: [
41
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/go/highlights.scm",
42
+ ],
43
+ locals: [
44
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/go/locals.scm",
45
+ ],
46
+ },
47
+ },
48
+ {
49
+ filetype: "cpp",
50
+ wasm: "https://github.com/tree-sitter/tree-sitter-cpp/releases/download/v0.23.4/tree-sitter-cpp.wasm",
51
+ queries: {
52
+ highlights: [
53
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/cpp/highlights.scm",
54
+ ],
55
+ locals: [
56
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/cpp/locals.scm",
57
+ ],
58
+ },
59
+ },
60
+ {
61
+ filetype: "csharp",
62
+ wasm: "https://github.com/tree-sitter/tree-sitter-c-sharp/releases/download/v0.23.1/tree-sitter-c_sharp.wasm",
63
+ queries: {
64
+ highlights: [
65
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/c_sharp/highlights.scm",
66
+ ],
67
+ locals: [
68
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/c_sharp/locals.scm",
69
+ ],
70
+ },
71
+ },
72
+ {
73
+ filetype: "bash",
74
+ wasm: "https://github.com/tree-sitter/tree-sitter-bash/releases/download/v0.25.0/tree-sitter-bash.wasm",
75
+ queries: {
76
+ highlights: [
77
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/bash/highlights.scm",
78
+ ],
79
+ },
80
+ },
81
+ {
82
+ filetype: "c",
83
+ wasm: "https://github.com/tree-sitter/tree-sitter-c/releases/download/v0.24.1/tree-sitter-c.wasm",
84
+ queries: {
85
+ highlights: [
86
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/c/highlights.scm",
87
+ ],
88
+ locals: [
89
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/c/locals.scm",
90
+ ],
91
+ },
92
+ },
93
+ {
94
+ filetype: "java",
95
+ wasm: "https://github.com/tree-sitter/tree-sitter-java/releases/download/v0.23.5/tree-sitter-java.wasm",
96
+ queries: {
97
+ highlights: [
98
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/java/highlights.scm",
99
+ ],
100
+ locals: [
101
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/java/locals.scm",
102
+ ],
103
+ },
104
+ },
105
+ {
106
+ filetype: "ruby",
107
+ wasm: "https://github.com/tree-sitter/tree-sitter-ruby/releases/download/v0.23.1/tree-sitter-ruby.wasm",
108
+ queries: {
109
+ highlights: [
110
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/ruby/highlights.scm",
111
+ ],
112
+ locals: [
113
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/ruby/locals.scm",
114
+ ],
115
+ },
116
+ },
117
+ {
118
+ filetype: "php",
119
+ wasm: "https://github.com/tree-sitter/tree-sitter-php/releases/download/v0.24.2/tree-sitter-php.wasm",
120
+ queries: {
121
+ highlights: [
122
+ // NOTE: This nvim-treesitter query is currently broken, because the parser is not compatible with the query apparently.
123
+ // "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/php/highlights.scm",
124
+ "https://github.com/tree-sitter/tree-sitter-php/raw/refs/heads/master/queries/highlights.scm",
125
+ ],
126
+ },
127
+ },
128
+ {
129
+ filetype: "scala",
130
+ wasm: "https://github.com/tree-sitter/tree-sitter-scala/releases/download/v0.24.0/tree-sitter-scala.wasm",
131
+ queries: {
132
+ highlights: [
133
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/scala/highlights.scm",
134
+ ],
135
+ },
136
+ },
137
+ {
138
+ filetype: "html",
139
+ wasm: "https://github.com/tree-sitter/tree-sitter-html/releases/download/v0.23.2/tree-sitter-html.wasm",
140
+ queries: {
141
+ highlights: [
142
+ // NOTE: This nvim-treesitter query is currently broken, because the parser is not compatible with the query apparently.
143
+ // "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/html/highlights.scm",
144
+ "https://github.com/tree-sitter/tree-sitter-html/raw/refs/heads/master/queries/highlights.scm",
145
+ ],
146
+ // TODO: Injections not working for some reason
147
+ // injections: [
148
+ // "https://github.com/tree-sitter/tree-sitter-html/raw/refs/heads/master/queries/injections.scm",
149
+ // ],
150
+ },
151
+ // injectionMapping: {
152
+ // nodeTypes: {
153
+ // script_element: "javascript",
154
+ // style_element: "css",
155
+ // },
156
+ // infoStringMap: {
157
+ // javascript: "javascript",
158
+ // css: "css",
159
+ // },
160
+ // },
161
+ },
162
+ {
163
+ filetype: "json",
164
+ wasm: "https://github.com/tree-sitter/tree-sitter-json/releases/download/v0.24.8/tree-sitter-json.wasm",
165
+ queries: {
166
+ highlights: [
167
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/json/highlights.scm",
168
+ ],
169
+ },
170
+ },
171
+ {
172
+ filetype: "yaml",
173
+ wasm: "https://github.com/tree-sitter-grammars/tree-sitter-yaml/releases/download/v0.7.2/tree-sitter-yaml.wasm",
174
+ queries: {
175
+ highlights: [
176
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/yaml/highlights.scm",
177
+ ],
178
+ },
179
+ },
180
+ {
181
+ filetype: "haskell",
182
+ wasm: "https://github.com/tree-sitter/tree-sitter-haskell/releases/download/v0.23.1/tree-sitter-haskell.wasm",
183
+ queries: {
184
+ highlights: [
185
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/haskell/highlights.scm",
186
+ ],
187
+ },
188
+ },
189
+ {
190
+ filetype: "css",
191
+ wasm: "https://github.com/tree-sitter/tree-sitter-css/releases/download/v0.25.0/tree-sitter-css.wasm",
192
+ queries: {
193
+ highlights: [
194
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/css/highlights.scm",
195
+ ],
196
+ },
197
+ },
198
+ {
199
+ filetype: "julia",
200
+ wasm: "https://github.com/tree-sitter/tree-sitter-julia/releases/download/v0.23.1/tree-sitter-julia.wasm",
201
+ queries: {
202
+ highlights: [
203
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/julia/highlights.scm",
204
+ ],
205
+ },
206
+ },
207
+ {
208
+ filetype: "ocaml",
209
+ wasm: "https://github.com/tree-sitter/tree-sitter-ocaml/releases/download/v0.24.2/tree-sitter-ocaml.wasm",
210
+ queries: {
211
+ highlights: [
212
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/ocaml/highlights.scm",
213
+ ],
214
+ },
215
+ },
216
+ {
217
+ filetype: "clojure",
218
+ wasm: "https://github.com/sogaiu/tree-sitter-clojure/releases/download/v0.0.13/tree-sitter-clojure.wasm",
219
+ queries: {
220
+ highlights: [
221
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/clojure/highlights.scm",
222
+ ],
223
+ },
224
+ },
225
+ {
226
+ filetype: "swift",
227
+ wasm: "https://github.com/alex-pinkus/tree-sitter-swift/releases/download/0.7.1/tree-sitter-swift.wasm",
228
+ queries: {
229
+ highlights: [
230
+ // NOTE: Using parser repo queries instead of nvim-treesitter due to incompatible #lua-match? predicates
231
+ // "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/highlights.scm
232
+ "https://raw.githubusercontent.com/alex-pinkus/tree-sitter-swift/main/queries/highlights.scm",
233
+ ],
234
+ locals: [
235
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/swift/locals.scm",
236
+ ],
237
+ },
238
+ },
239
+ {
240
+ filetype: "nix",
241
+ // TODO: Replace with official tree-sitter-nix WASM when published
242
+ // See: https://github.com/nix-community/tree-sitter-nix/issues/66
243
+ wasm: "https://github.com/ast-grep/ast-grep.github.io/raw/40b84530640aa83a0d34a20a2b0623d7b8e5ea97/website/public/parsers/tree-sitter-nix.wasm",
244
+ queries: {
245
+ highlights: [
246
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/nix/highlights.scm",
247
+ ],
248
+ locals: [
249
+ "https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/nix/locals.scm",
250
+ ],
251
+ },
252
+ },
253
+ ],
254
+ }
@@ -3,13 +3,17 @@
3
3
  // Renders example hunks and review data to preview TUI appearance.
4
4
  // Run with: bun run scripts/preview-review.tsx (TUI) or --web (HTML upload).
5
5
 
6
- import { createCliRenderer } from "@opentui/core"
6
+ import { createCliRenderer, addDefaultParsers } from "@opentui/core"
7
+ import parsersConfig from "../parsers-config.ts"
8
+
9
+ // Register custom syntax highlighting parsers
10
+ addDefaultParsers(parsersConfig.parsers)
7
11
  import { createRoot } from "@opentui/react"
8
12
  import * as React from "react"
9
13
  import { ReviewApp, ReviewAppView } from "../src/review/review-app.tsx"
10
14
  import { createHunk } from "../src/review/hunk-parser.ts"
11
15
  import type { ReviewYaml } from "../src/review/types.ts"
12
- import { captureReviewResponsiveHtml, uploadHtml } from "../src/web-utils.ts"
16
+ import { captureReviewResponsiveHtml, uploadHtml } from "../src/web-utils.tsx"
13
17
  import fs from "fs"
14
18
  import { tmpdir } from "os"
15
19
  import { join } from "path"
@@ -89,6 +93,35 @@ const exampleHunks = [
89
93
  " })",
90
94
  " })",
91
95
  ]),
96
+ // Rust example
97
+ createHunk(5, "src/lib.rs", 0, 1, 1, [
98
+ "+use std::collections::HashMap;",
99
+ "+use std::sync::Arc;",
100
+ "+",
101
+ "+#[derive(Debug, Clone)]",
102
+ "+pub struct User {",
103
+ "+ pub id: u64,",
104
+ "+ pub name: String,",
105
+ "+ pub email: Option<String>,",
106
+ "+}",
107
+ "+",
108
+ "+impl User {",
109
+ "+ pub fn new(id: u64, name: impl Into<String>) -> Self {",
110
+ "+ Self {",
111
+ "+ id,",
112
+ "+ name: name.into(),",
113
+ "+ email: None,",
114
+ "+ }",
115
+ "+ }",
116
+ "+",
117
+ "+ pub fn with_email(mut self, email: impl Into<String>) -> Self {",
118
+ "+ self.email = Some(email.into());",
119
+ "+ self",
120
+ "+ }",
121
+ "+}",
122
+ "+",
123
+ "+pub type UserCache = Arc<HashMap<u64, User>>;",
124
+ ]),
92
125
  ]
93
126
 
94
127
  // Rich review data with multiple sections
@@ -206,6 +239,23 @@ Added test case for the new error handling behavior, ensuring that:
206
239
 
207
240
  All tests pass and coverage is at 95%.`,
208
241
  },
242
+ {
243
+ hunkIds: [5],
244
+ markdownDescription: `## Rust User Model
245
+
246
+ Added a Rust implementation of the User model with:
247
+
248
+ - **Derive macros**: \`Debug\` and \`Clone\` for easy debugging and copying
249
+ - **Builder pattern**: Fluent API with \`with_email()\` method
250
+ - **Type alias**: \`UserCache\` for thread-safe shared storage
251
+
252
+ \`\`\`rust
253
+ let user = User::new(1, "Alice")
254
+ .with_email("alice@example.com");
255
+ \`\`\`
256
+
257
+ This provides a type-safe, zero-cost abstraction for user data.`,
258
+ },
209
259
  ],
210
260
  }
211
261
 
package/src/ansi-html.ts CHANGED
@@ -170,7 +170,10 @@ export function frameToHtmlDocument(frame: CapturedFrame, options: ToHtmlOptions
170
170
  <html>
171
171
  <head>
172
172
  <meta charset="utf-8">
173
- <meta name="viewport" content="width=device-width, initial-scale=1">${ogTags}
173
+ <meta name="viewport" content="width=device-width, initial-scale=1">
174
+ <link rel="icon" href="/favicon-dark.png" media="(prefers-color-scheme: dark)">
175
+ <link rel="icon" href="/favicon-light.png" media="(prefers-color-scheme: light)">
176
+ <link rel="icon" href="/favicon-dark.png">${ogTags}
174
177
  <style>
175
178
  @font-face {
176
179
  font-family: 'JetBrains Mono Nerd';
Binary file
Binary file
package/src/cli.tsx CHANGED
@@ -21,7 +21,12 @@ import {
21
21
  MacOSScrollAccel,
22
22
  ScrollBoxRenderable,
23
23
  BoxRenderable,
24
+ addDefaultParsers,
24
25
  } from "@opentui/core";
26
+ import parsersConfig from "../parsers-config.ts";
27
+
28
+ // Register custom syntax highlighting parsers
29
+ addDefaultParsers(parsersConfig.parsers);
25
30
  import fs from "fs";
26
31
  import { tmpdir } from "os";
27
32
  import { join } from "path";
@@ -520,9 +520,11 @@ describe("buildGitCommand with rename detection", () => {
520
520
  expect(cmd).toContain("-M")
521
521
  })
522
522
 
523
- it("should include -M flag in single base show", () => {
523
+ it("should use git diff for single base (compare to working tree)", () => {
524
524
  const cmd = buildGitCommand({ base: "HEAD~1" })
525
525
  expect(cmd).toContain("-M")
526
+ expect(cmd).toStartWith("git diff HEAD~1")
527
+ expect(cmd).not.toContain("git show")
526
528
  })
527
529
 
528
530
  it("should include -M flag in three-dot range", () => {
package/src/diff-utils.ts CHANGED
@@ -256,9 +256,9 @@ export function buildGitCommand(options: GitCommandOptions): string {
256
256
  return `git diff ${rangeBase}..${rangeHead} --no-prefix ${renameArg} ${submoduleArg} ${contextArg} ${filterArg}`.trim();
257
257
  }
258
258
  }
259
- // Single ref: show that commit's changes
259
+ // Single ref: compare ref to working tree (like git diff)
260
260
  if (options.base) {
261
- return `git show ${options.base} --no-prefix ${renameArg} ${submoduleArg} ${contextArg} ${filterArg}`.trim();
261
+ return `git diff ${options.base} --no-prefix ${renameArg} ${submoduleArg} ${contextArg} ${filterArg}`.trim();
262
262
  }
263
263
  return `git add -N . && git diff --no-prefix ${renameArg} ${submoduleArg} ${contextArg} ${filterArg}`.trim();
264
264
  }
@@ -406,7 +406,7 @@ export function processFiles<T extends ParsedFile>(
406
406
 
407
407
  /**
408
408
  * Detect filetype from filename for syntax highlighting
409
- * Maps to tree-sitter parsers available in @opentui/core: typescript, javascript, markdown, zig
409
+ * Maps to tree-sitter parsers available in @opentui/core and parsers-config.ts
410
410
  */
411
411
  export function detectFiletype(filePath: string): string | undefined {
412
412
  const ext = filePath.split(".").pop()?.toLowerCase();
@@ -421,14 +421,73 @@ export function detectFiletype(filePath: string): string | undefined {
421
421
  case "mts":
422
422
  case "cts":
423
423
  return "typescript";
424
- // JSON uses JavaScript parser (JSON is valid JS)
425
424
  case "json":
426
- return "javascript";
425
+ return "json";
427
426
  case "md":
428
427
  case "mdx":
429
428
  return "markdown";
430
429
  case "zig":
431
430
  return "zig";
431
+ // Languages from parsers-config.ts
432
+ case "py":
433
+ case "pyw":
434
+ case "pyi":
435
+ return "python";
436
+ case "rs":
437
+ return "rust";
438
+ case "go":
439
+ return "go";
440
+ case "cpp":
441
+ case "cc":
442
+ case "cxx":
443
+ case "hpp":
444
+ case "hxx":
445
+ case "h":
446
+ return "cpp";
447
+ case "cs":
448
+ return "csharp";
449
+ case "sh":
450
+ case "bash":
451
+ case "zsh":
452
+ return "bash";
453
+ case "c":
454
+ return "c";
455
+ case "java":
456
+ return "java";
457
+ case "rb":
458
+ case "rake":
459
+ case "gemspec":
460
+ return "ruby";
461
+ case "php":
462
+ return "php";
463
+ case "scala":
464
+ case "sc":
465
+ return "scala";
466
+ case "html":
467
+ case "htm":
468
+ return "html";
469
+ case "yaml":
470
+ case "yml":
471
+ return "yaml";
472
+ case "hs":
473
+ case "lhs":
474
+ return "haskell";
475
+ case "css":
476
+ return "css";
477
+ case "jl":
478
+ return "julia";
479
+ case "ml":
480
+ case "mli":
481
+ return "ocaml";
482
+ case "clj":
483
+ case "cljs":
484
+ case "cljc":
485
+ case "edn":
486
+ return "clojure";
487
+ case "swift":
488
+ return "swift";
489
+ case "nix":
490
+ return "nix";
432
491
  default:
433
492
  return undefined;
434
493
  }
package/src/web-utils.tsx CHANGED
@@ -109,7 +109,7 @@ function renderExpiryNotice(options: { textColor: string; mutedColor: string })
109
109
  <box style={{ flexDirection: "column", paddingBottom: 1, paddingLeft: 1 }}>
110
110
  <box style={{ flexDirection: "row" }}>
111
111
  <text fg={options.textColor}>This page will expire in 7 days. </text>
112
- <text fg={options.mutedColor}>Get unlimited links: </text>
112
+ <text fg={options.textColor}>Get unlimited links: </text>
113
113
  <text fg={options.textColor}>{buyUrl}</text>
114
114
  </box>
115
115
  </box>
package/src/worker.tsx CHANGED
@@ -33,6 +33,11 @@ const app = new Hono<{ Bindings: Bindings }>()
33
33
  const SEVEN_DAYS = 60 * 60 * 24 * 7
34
34
  const LICENSE_HEADER = "X-Critique-License"
35
35
  const STRIPE_YEARLY_PRICE_ID = "price_1Su9CZBekrVyz93iMIEnjPOk"
36
+
37
+ // Favicon PNGs (32x32) - dark (white icon for dark bg) and light (black icon for light bg)
38
+ const FAVICON_DARK_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA8ElEQVR4nO2WPwrCMBhHSxVBvIR4CsHFQXsGt6KjJyiouOosOLgI7s4OjtrjODv450lLSjPFtjZRbB/8hi9pksdHCrGskgQAV95zAQaWDkjOHRjpEOgAPUX6gC8knsA4d4l3AA3gKHVj+slmfoq236LDgDpwkOZmWQWysBBra8BeGl+aEpAlKsCOmDVgmxAImEsSW2JWpgRkCRvYhCPwMCkQMBF7dcMKyFNgKKLiplOgKaLk7wRcoCVSFYlqtxACMsW8A2l/Q60Cifg7AUfxvWNCwFM8yTwTAqn5FYG2KK9fEcgEpQCcc2jCKVv/rYLxAsud37oBTmHeAAAAAElFTkSuQmCC"
39
+ const FAVICON_LIGHT_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAA8klEQVR4nO2WPQrCQBCFP1QE8RLiKQQbC/UMdqKlJxBUbLUWLGwEe2sLS/U41hb+jAgjTJXE1WzE+ODB5mV38mWWhYW/oukESIiPQIuYJBF9AbpxAFSBeoAbwEEhbkCPBFQEtqYbw3eKHV5o+9l8rABszLuRK4A4eKJr88Da5FNfAGIgssDK5HMg4wNAgLGBWJp85gtADMTjrxeaXX0CCDDQWjWTfQygow47HbEBlNRh834LoA2U1Tn187mdCgBJegvkmwA6EY5hrAAS0b8F0AyY3/QB0A+4kvVTsQXiCFDR8SkpACdJ6gH2H+jAzq35pEx3Rh7HfgGTRuMAAAAASUVORK5CYII="
40
+
36
41
  const logger = {
37
42
  log: (...args: unknown[]) => {
38
43
  console.error(...args)
@@ -47,6 +52,33 @@ app.get("/", (c) => {
47
52
  return c.redirect("https://github.com/remorses/critique")
48
53
  })
49
54
 
55
+ // Serve favicon - dark version (white icon for dark backgrounds)
56
+ app.get("/favicon-dark.png", (c) => {
57
+ const bytes = Uint8Array.from(atob(FAVICON_DARK_BASE64), (c) => c.charCodeAt(0))
58
+ return c.body(bytes, 200, {
59
+ "Content-Type": "image/png",
60
+ "Cache-Control": "public, max-age=604800",
61
+ })
62
+ })
63
+
64
+ // Serve favicon - light version (black icon for light backgrounds)
65
+ app.get("/favicon-light.png", (c) => {
66
+ const bytes = Uint8Array.from(atob(FAVICON_LIGHT_BASE64), (c) => c.charCodeAt(0))
67
+ return c.body(bytes, 200, {
68
+ "Content-Type": "image/png",
69
+ "Cache-Control": "public, max-age=604800",
70
+ })
71
+ })
72
+
73
+ // Default favicon.ico - returns dark version
74
+ app.get("/favicon.ico", (c) => {
75
+ const bytes = Uint8Array.from(atob(FAVICON_DARK_BASE64), (c) => c.charCodeAt(0))
76
+ return c.body(bytes, 200, {
77
+ "Content-Type": "image/png",
78
+ "Cache-Control": "public, max-age=604800",
79
+ })
80
+ })
81
+
50
82
  function requireEnv(value: string | undefined, name: string): string {
51
83
  if (!value) {
52
84
  throw new Error(`Missing env var ${name}`)
@@ -176,7 +208,7 @@ function generateLicenseKey(): string {
176
208
  }
177
209
 
178
210
  function buildLicenseCommand(licenseKey: string): string {
179
- return `npx ciritque login ${licenseKey}`
211
+ return `npx critique login ${licenseKey}`
180
212
  }
181
213
 
182
214
  async function sendLicenseEmail(
@@ -201,7 +233,7 @@ async function sendLicenseEmail(
201
233
  <div style="font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif; color: #0f172a;">
202
234
  <p>Thanks for subscribing to Critique.</p>
203
235
  <p>Run this on any machine where you want to use critique:</p>
204
- <pre style="background: #0b1117; color: #e6edf3; padding: 12px 14px; border-radius: 8px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;">${command}</pre>
236
+ <pre style="background: #0b1117; color: #e6edf3; padding: 12px 14px; border-radius: 8px; font-family: 'Courier New', Courier, monospace;">${command}</pre>
205
237
  <p>This unlocks permanent critique links.</p>
206
238
  </div>
207
239
  `
package/tsconfig.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "exclude": ["opensrc"],
2
+ "exclude": ["opensrc", "tmp"],
3
3
  "compilerOptions": {
4
4
  // Environment setup & latest features
5
5
  "lib": ["ESNext"],