critique 0.1.11 → 0.1.13
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 +14 -0
- package/README.md +34 -15
- package/package.json +1 -1
- package/screenshot-web.png +0 -0
- package/src/ansi-html.ts +11 -0
- package/src/worker.ts +30 -16
- package/tmp/playwriter-screenshot-1768239285679-m7ha.jpg +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# 0.1.13
|
|
2
|
+
|
|
3
|
+
- Docs:
|
|
4
|
+
- Enhance web preview section in README
|
|
5
|
+
- Add `/v/` short URL alias documentation
|
|
6
|
+
|
|
7
|
+
# 0.1.12
|
|
8
|
+
|
|
9
|
+
- Web preview:
|
|
10
|
+
- Add client-side mobile detection with redirect to `?v=mobile`
|
|
11
|
+
- Simplify worker: redirect mobile devices instead of content negotiation
|
|
12
|
+
- Remove `Vary` header - URL now determines content, better caching
|
|
13
|
+
- Increase cache max-age to 24h (was 1h)
|
|
14
|
+
|
|
1
15
|
# 0.1.11
|
|
2
16
|
|
|
3
17
|
- All commands:
|
package/README.md
CHANGED
|
@@ -86,7 +86,13 @@ Use the interactive UI to select files. Selected files are immediately applied a
|
|
|
86
86
|
|
|
87
87
|
### Web Preview
|
|
88
88
|
|
|
89
|
-
Generate a shareable web preview of your diff that you can send to anyone - no installation required
|
|
89
|
+
Generate a shareable web preview of your diff that you can send to anyone - no installation required.
|
|
90
|
+
|
|
91
|
+
**Example:** [critique.work/v/b8faf4362c247bfc46f5098a028e00f0](https://critique.work/v/b8faf4362c247bfc46f5098a028e00f0)
|
|
92
|
+
|
|
93
|
+
Great for background agents that can't render terminal UIs, like [kimaki.xyz](https://kimaki.xyz) which runs OpenCode in Discord.
|
|
94
|
+
|
|
95
|
+

|
|
90
96
|
|
|
91
97
|
```bash
|
|
92
98
|
# Upload to critique.work and get a shareable URL
|
|
@@ -95,29 +101,32 @@ critique web
|
|
|
95
101
|
# View staged changes
|
|
96
102
|
critique web --staged
|
|
97
103
|
|
|
98
|
-
# View the last commit
|
|
104
|
+
# View the last commit
|
|
99
105
|
critique web HEAD
|
|
100
106
|
|
|
101
107
|
# View a specific commit
|
|
102
108
|
critique web --commit HEAD~1
|
|
103
109
|
|
|
104
|
-
#
|
|
105
|
-
critique web
|
|
110
|
+
# Compare branches (PR-style diff)
|
|
111
|
+
critique web main feature-branch
|
|
112
|
+
|
|
113
|
+
# Filter specific files
|
|
114
|
+
critique web -- src/api.ts src/utils.ts
|
|
115
|
+
|
|
116
|
+
# Custom title for the HTML page
|
|
117
|
+
critique web --title "Fix authentication bug"
|
|
106
118
|
|
|
107
119
|
# Generate local HTML file instead of uploading
|
|
108
120
|
critique web --local
|
|
109
|
-
|
|
110
|
-
# Adjust rendering size (use ~100 cols for mobile-friendly output)
|
|
111
|
-
critique web --cols 100 --rows 2000
|
|
112
121
|
```
|
|
113
122
|
|
|
114
|
-
**
|
|
123
|
+
**Features:**
|
|
115
124
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
- **Mobile optimized** - Automatically detects mobile devices and serves a unified diff view optimized for smaller screens. Add `?v=mobile` to any URL to force mobile view.
|
|
126
|
+
- **Dark/Light mode** - Automatically adapts to your system's color scheme preference using CSS `prefers-color-scheme`.
|
|
127
|
+
- **Syntax highlighting** - Full syntax highlighting for 18+ languages, same as the terminal UI.
|
|
128
|
+
- **Split view** - Side-by-side diff on desktop, unified view on mobile.
|
|
129
|
+
- **Fast loading** - HTML is streamed for quick initial render, cached for 24 hours.
|
|
121
130
|
|
|
122
131
|
**Options:**
|
|
123
132
|
|
|
@@ -126,14 +135,24 @@ critique web --cols 100 --rows 2000
|
|
|
126
135
|
| `--staged` | Show staged changes | - |
|
|
127
136
|
| `--commit <ref>` | Show changes from a specific commit | - |
|
|
128
137
|
| `--cols <n>` | Terminal width for rendering | `240` |
|
|
129
|
-
| `--
|
|
138
|
+
| `--mobile-cols <n>` | Terminal width for mobile version | `100` |
|
|
130
139
|
| `--local` | Save HTML locally instead of uploading | - |
|
|
131
140
|
| `--filter <pattern>` | Filter files by glob (can be used multiple times) | - |
|
|
141
|
+
| `--title <text>` | Custom HTML document title | `Critique Diff` |
|
|
142
|
+
| `--theme <name>` | Theme for rendering (disables auto dark/light mode) | - |
|
|
143
|
+
|
|
144
|
+
**How it works:**
|
|
145
|
+
|
|
146
|
+
1. Captures the terminal UI output using a PTY (pseudo-terminal)
|
|
147
|
+
2. Converts ANSI escape codes to styled HTML with syntax highlighting
|
|
148
|
+
3. Generates both desktop (240 cols, split view) and mobile (100 cols, unified view) versions
|
|
149
|
+
4. Uploads to [critique.work](https://critique.work) (Cloudflare Worker + KV storage)
|
|
150
|
+
5. Returns a shareable URL that expires after 7 days
|
|
132
151
|
|
|
133
152
|
**Tips:**
|
|
134
153
|
|
|
135
|
-
- Use `--cols 100` for mobile-friendly output (switches to unified diff view instead of split view)
|
|
136
154
|
- The URL is based on a SHA-256 hash of the content, so identical diffs produce the same URL (deduplication)
|
|
155
|
+
- Use `?v=desktop` or `?v=mobile` query params to force a specific version
|
|
137
156
|
- If upload fails, critique automatically saves the HTML locally as a fallback
|
|
138
157
|
|
|
139
158
|
## Features
|
package/package.json
CHANGED
|
Binary file
|
package/src/ansi-html.ts
CHANGED
|
@@ -204,6 +204,17 @@ ${content}
|
|
|
204
204
|
const minFontSize = 4;
|
|
205
205
|
const maxFontSize = 16;
|
|
206
206
|
|
|
207
|
+
// Redirect mobile devices to ?v=mobile for optimized view
|
|
208
|
+
// Only redirect if not already on a forced version
|
|
209
|
+
const params = new URLSearchParams(window.location.search);
|
|
210
|
+
if (!params.has('v')) {
|
|
211
|
+
const isMobile = /Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|Opera M(obi|ini)|Windows Phone|webOS/i.test(navigator.userAgent);
|
|
212
|
+
if (isMobile) {
|
|
213
|
+
params.set('v', 'mobile');
|
|
214
|
+
window.location.replace(window.location.pathname + '?' + params.toString());
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
207
218
|
function adjustFontSize() {
|
|
208
219
|
const viewportWidth = window.innerWidth;
|
|
209
220
|
const calculatedSize = (viewportWidth - padding) / (cols * charRatio);
|
package/src/worker.ts
CHANGED
|
@@ -80,7 +80,7 @@ app.post("/upload", async (c) => {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
const url = new URL(c.req.url)
|
|
83
|
-
const viewUrl = `${url.origin}/
|
|
83
|
+
const viewUrl = `${url.origin}/v/${id}`
|
|
84
84
|
|
|
85
85
|
return c.json({ id, url: viewUrl })
|
|
86
86
|
} catch (error) {
|
|
@@ -89,21 +89,31 @@ app.post("/upload", async (c) => {
|
|
|
89
89
|
})
|
|
90
90
|
|
|
91
91
|
// View HTML content with streaming
|
|
92
|
-
// GET /view/:id
|
|
93
|
-
// Query params: ?v=desktop or ?v=mobile to
|
|
94
|
-
|
|
92
|
+
// GET /v/:id (short) or /view/:id (legacy)
|
|
93
|
+
// Query params: ?v=desktop or ?v=mobile to select version
|
|
94
|
+
// Server redirects mobile devices to ?v=mobile, client JS also handles redirect
|
|
95
|
+
async function handleView(c: any) {
|
|
95
96
|
const id = c.req.param("id")
|
|
96
97
|
|
|
97
98
|
if (!id || !/^[a-f0-9]{16,32}$/.test(id)) {
|
|
98
99
|
return c.text("Invalid ID", 400)
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
// Check for
|
|
102
|
-
const
|
|
103
|
-
|
|
102
|
+
// Check for version query param
|
|
103
|
+
const version = c.req.query("v")
|
|
104
|
+
|
|
105
|
+
// If no version specified and mobile device detected, redirect to ?v=mobile
|
|
106
|
+
// This is a fallback - client JS also handles this redirect
|
|
107
|
+
if (!version && isMobileDevice(c)) {
|
|
108
|
+
const url = new URL(c.req.url)
|
|
109
|
+
url.searchParams.set("v", "mobile")
|
|
110
|
+
return c.redirect(url.toString(), 302)
|
|
111
|
+
}
|
|
104
112
|
|
|
105
|
-
//
|
|
113
|
+
// Serve the appropriate version based on query param
|
|
114
|
+
const isMobile = version === "mobile"
|
|
106
115
|
let html: string | null = null
|
|
116
|
+
|
|
107
117
|
if (isMobile) {
|
|
108
118
|
// Try mobile version first, fall back to desktop
|
|
109
119
|
html = await c.env.CRITIQUE_KV.get(`${id}-mobile`)
|
|
@@ -120,11 +130,9 @@ app.get("/view/:id", async (c) => {
|
|
|
120
130
|
|
|
121
131
|
// Stream the HTML content for faster initial load
|
|
122
132
|
return stream(c, async (s) => {
|
|
123
|
-
// Set content type header
|
|
124
133
|
c.header("Content-Type", "text/html; charset=utf-8")
|
|
125
|
-
//
|
|
126
|
-
c.header("
|
|
127
|
-
c.header("Cache-Control", "public, max-age=3600")
|
|
134
|
+
// Cache is now safe - URL determines content, no Vary needed
|
|
135
|
+
c.header("Cache-Control", "public, max-age=86400")
|
|
128
136
|
|
|
129
137
|
// Stream in chunks for better performance
|
|
130
138
|
const chunkSize = 16 * 1024 // 16KB chunks
|
|
@@ -136,7 +144,10 @@ app.get("/view/:id", async (c) => {
|
|
|
136
144
|
offset += chunkSize
|
|
137
145
|
}
|
|
138
146
|
})
|
|
139
|
-
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
app.get("/v/:id", handleView)
|
|
150
|
+
app.get("/view/:id", handleView)
|
|
140
151
|
|
|
141
152
|
// Get raw HTML content (for debugging/API access)
|
|
142
153
|
// GET /raw/:id
|
|
@@ -159,8 +170,8 @@ app.get("/raw/:id", async (c) => {
|
|
|
159
170
|
})
|
|
160
171
|
|
|
161
172
|
// Check if content exists
|
|
162
|
-
// HEAD /view/:id
|
|
163
|
-
|
|
173
|
+
// HEAD /v/:id or /view/:id
|
|
174
|
+
async function handleHead(c: any) {
|
|
164
175
|
const id = c.req.param("id")
|
|
165
176
|
|
|
166
177
|
if (!id || !/^[a-f0-9]{16,32}$/.test(id)) {
|
|
@@ -175,6 +186,9 @@ app.on("HEAD", "/view/:id", async (c) => {
|
|
|
175
186
|
|
|
176
187
|
c.header("Content-Length", String(html.length))
|
|
177
188
|
return c.body(null, 200)
|
|
178
|
-
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
app.on("HEAD", "/v/:id", handleHead)
|
|
192
|
+
app.on("HEAD", "/view/:id", handleHead)
|
|
179
193
|
|
|
180
194
|
export default app
|
|
Binary file
|