retake-dev 0.4.0 → 0.5.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/CHANGELOG.md ADDED
@@ -0,0 +1,125 @@
1
+ # Changelog
2
+
3
+ ## 0.5.1
4
+
5
+ - Notes on animations work the way web animation does: per element, on the
6
+ animation's own clock. ⌘-click an element and it gets a row on the track with
7
+ its own animations; open one to see its clock (0 after the delay), its
8
+ keyframes and what it moves, and the path it takes on the app. Click to pin a
9
+ note at a point ("at 100ms (20%) of fadeUp"), drag to pin it over a range
10
+ ("200–400ms"), or Shift+drag the timeline first. Numbers typed in the note are
11
+ read on that clock. Copy for agent and `get_note` now give the animation's
12
+ keyframes and timing, the exact point or range with its keyframe segment, the
13
+ values and the box there, and how to change only that part.
14
+ - Each animation note now carries an exact edit: the keyframes again, on plain
15
+ time, with the note's point or range edges as keyframes of their own and the
16
+ exact part of each curve on both sides (written for CSS `@keyframes`,
17
+ `element.animate()` or Motion's `times`/`ease`). Change the marked part and the
18
+ rest of the motion stays as it was, even when the easing is on the whole
19
+ effect. A `@keyframes` that runs on several elements says so, and the edit
20
+ gives the noted element its own copy.
21
+ - Motion keyframes (`x: [0, 120, 120, 240]` with `times`) are read off the
22
+ motion element's props: every keyframe, its times and the ease of each
23
+ segment, so a range over a hold is on one clip and the exact edit is in
24
+ Motion's own terms. A CSS transition's exact edit is its timing as `linear()`
25
+ stops with the range edges marked. Two animations over the same time on one
26
+ element (a transform and an opacity transition) stack on its row, so either
27
+ can be opened.
28
+ - Script-driven motion is recorded: GSAP tweens, Motion's `x`/`y`/`scale` and
29
+ react-spring write inline styles every frame, and now show as clips (kind
30
+ `js`) with their first and last values. Motion and GSAP are named, with
31
+ Motion's `animate`/`transition` props in the note; `element.animate()` clips
32
+ say where they were called. A note after an animation ended is about its end
33
+ state. CSS animations from `<link>` stylesheets (Next, any framework) get
34
+ their `@keyframes` file and line, and source paths are relative to the project.
35
+ - ⌘-click picks what you can see under the pointer: text, media, a control or a
36
+ painted box come first; empty overlays (glows, stretched links, scrims) and
37
+ invisible layers (opacity 0, a closed menu sheet) are a wheel step away, shown
38
+ dimmed with why. The highlight and the note always agree (⌘ pressed with the
39
+ pointer already still, or a build swapping in while ⌘ is held). An svg icon is
40
+ the `<svg>` or its button, not the section around it; shadow DOM and embedded
41
+ iframes are pickable (an embed gets no clicks while paused). A note's clip is
42
+ only ever its own element's (or an ancestor's that moves it). Selectors leave
43
+ out generated ids and classes that came and went during the recording, so they
44
+ find the element on a fresh load. A server component's element gets its own
45
+ source line (`GET /__retake/map`), or "unknown", never its client parent's.
46
+ Next's layout components are left out of component names. A second ⌘-click on
47
+ a pinned spot makes a new note there.
48
+ - `retake mcp`: `get_animation` maps recording times onto an animation's own
49
+ clock (and into CSS %, Motion `times`, GSAP seconds); `get_moment` on a note
50
+ takes the note's word for which animations are its element's, so it can't
51
+ disagree with `get_note`; `list_notes` shows each note's range and animation.
52
+ The server's instructions carry a short guide to editing each kind of animation.
53
+ - Next.js: the timeline on your usual dev URL. Add `proxy.ts` with
54
+ `export { default } from "retake-dev/next"` (Next 16), or `middleware.ts` with the
55
+ Node.js runtime (Next 15), and run `next dev` as always: no second port. Already
56
+ have one? `export default withRetake(yourMiddleware)`. Sessions, notes and
57
+ `retake mcp` work as with the CLI. Dev only: `next build` output has no dock or
58
+ runtime. `npx retake-dev .` still works without installing anything.
59
+ - Large recordings upload gzipped (a Next.js middleware reads 10 MB of a request at most).
60
+ - The dock is open on a first visit, and folds away: drag its divider down (or
61
+ press ⌥T) and the timeline becomes a round button, bottom-right at first. The
62
+ dock follows the pointer all the way down; let go near the bottom and it eases
63
+ into the button, let go higher and it springs back to its minimum height. The
64
+ app gets the whole window and recording carries on. The button (Enter/Space
65
+ too) brings the dock back at its default height. Remembered across reloads.
66
+ - Drag the button anywhere (mouse or finger): it eases to the nearer side edge,
67
+ stays on screen through a resize, and keeps its spot across reloads. A press
68
+ that barely moves is still a click.
69
+ - A new mark on the button (a playhead on a track with an arc back to it), with
70
+ a dot for the phase: green live, blue playing, amber paused. `markSvg()`
71
+ (exported from `retake-dev`) returns it as an SVG string.
72
+ - Timelines are named "Timeline 1", "Timeline 2"... (they were "Main", "Take 2"...).
73
+ Sessions saved with the old default names show the new ones (in the dock and
74
+ through `retake mcp`); names you gave them stay. A new timeline says so:
75
+ "Timeline 2 started".
76
+ - Note pins show on the app only while paused; live or playing, only the count shows.
77
+ - Phones and touch screens: the dock's top row fits at 360px (Start fresh shrinks
78
+ to its icon), its controls are 40px, and the divider takes a finger drag (to
79
+ resize or fold). Before, a touch drag on it left the app ignoring taps.
80
+ - ⌥T typed in a note or a timeline's name no longer folds the dock.
81
+ - The app can leave room for the dock: in its frame, `window.__retakeDockHeight`
82
+ is the height the dock covers (0 when folded), and a `retake:dock` event says
83
+ when it changes.
84
+ - `shellHtml({ server: false })` for a dock with no Retake server behind it: it
85
+ never requests `/__retake/` (no session 404 in the console).
86
+ - Leaving the page while recording (a new address typed in) no longer loses the
87
+ seconds since the last save: they're kept in the tab's sessionStorage and the
88
+ timeline carries on from them.
89
+ - `retake mcp` reads the recording: `get_moment` (around a note's moment, or any
90
+ moment of a timeline) and `get_timeline_events` (a stretch of a timeline) list
91
+ the user's actions, requests, route changes, the animations on or near an
92
+ element with their start, end and what they animate between, and when the
93
+ screen changed most. `get_note` gives the animation's start and end too, and
94
+ points to `get_moment`.
95
+ - A note's source in a bundled app (Next.js on Turbopack or webpack) is the
96
+ app's file and line: the dock reads the chunk's source map, index maps
97
+ included, and skips frames that land in the bundled JSX runtime. With no map
98
+ it says the source isn't known (`retake mcp` tries the map again) instead of
99
+ naming the chunk.
100
+ - "Copy for agent" starts with two lines saying what a Retake note is and how to
101
+ see what happened around its moment.
102
+ - Recorded animations keep their first and last keyframe values (`from`/`to`),
103
+ delay and per-iteration duration.
104
+ - A second skill, `retake-notes`: how a coding agent works through notes.
105
+
106
+ ## 0.5.0
107
+
108
+ The dock folds into an icon you can drag anywhere, notes show only when paused, and timelines are named Timeline 1, Timeline 2. Everything above builds on it.
109
+
110
+ ## 0.4.0
111
+
112
+ First release.
113
+
114
+ - A timeline docked over your dev server's app, recording from page load: inputs,
115
+ a virtual clock (timers, rAF, `Date`, CSS and Web Animations), seeded randomness,
116
+ and server traffic (`fetch`, XHR, `EventSource`, `WebSocket`).
117
+ - Drag back and the app is at that moment: a live preview at once, the real
118
+ moment rebuilt behind it and swapped in.
119
+ - Ctrl-click (or +) to start a new take from any moment; the old one stays a lane.
120
+ - Notes on elements at a moment, with component, source file:line and CSS;
121
+ "Copy for agent", and an MCP server (`retake mcp`) for coding agents.
122
+ - Vite plugin (`retake()`), and a front server for Next.js, React Router, Remix,
123
+ Astro, SvelteKit, Nuxt or any dev server that serves HTML (`retake .`,
124
+ `retake -- <cmd>`, `retake http://…`).
125
+ - `--code-branches`: each timeline keeps its own version of the code (Vite apps).
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Retake
2
2
 
3
3
  A time machine for your dev server: Vite apps, Next.js, React Router and other
4
- frameworks. A timeline docks over the bottom of your app and records everything from page load. Drag it back and the app is at that
4
+ frameworks. A timeline docks over the bottom of your app and records from page load. Drag it back and the app is at that
5
5
  moment. Ctrl-click the timeline to start a new take from there; the old one
6
6
  stays as a lane you can click back into. Dev only: nothing ships in builds.
7
7
 
@@ -51,6 +51,60 @@ export default defineConfig({
51
51
 
52
52
  `retake()` only runs in `vite dev`; `vite build` output has no Retake code in it.
53
53
 
54
+ ### Next.js: on your usual dev URL
55
+ Install `retake-dev` (above) and add one file in the project root (in `src/` if
56
+ your app lives in `src/app`). Then run `next dev` / `npm run dev` as always: the
57
+ timeline shows on your normal dev URL (http://localhost:3000), no second port.
58
+
59
+ ```ts
60
+ // proxy.ts (Next 16)
61
+ export { default } from "retake-dev/next"
62
+ ```
63
+
64
+ ```ts
65
+ // middleware.ts (Next 15.5)
66
+ import retake from "retake-dev/next"
67
+ export default retake
68
+ export const config = { runtime: "nodejs" }
69
+ ```
70
+
71
+ Install it from the registry (or a tarball, `npm i ../retake-dev-0.5.1.tgz`): a
72
+ `file:` or linked install is a symlink, which Turbopack doesn't follow out of the
73
+ project, and `retake-dev/next` isn't found.
74
+
75
+ Next 15 needs the Node.js runtime (Retake keeps `.retake/` on disk). It only runs in
76
+ `next dev`: in `next build` / `next start` every request goes straight on, and the
77
+ build has no dock or runtime in it.
78
+
79
+ Already have a proxy or middleware? Wrap yours:
80
+
81
+ ```ts
82
+ import { withRetake } from "retake-dev/next"
83
+
84
+ export default withRetake(async (req) => {
85
+ // ...your middleware, as before
86
+ })
87
+ ```
88
+
89
+ Next reads `config` from your file as written (it can't be re-exported). Without one
90
+ the proxy runs for every request and passes everything but page loads on. With a
91
+ `matcher` of your own, add Retake's two entries to it:
92
+
93
+ ```ts
94
+ export const config = {
95
+ matcher: [
96
+ "/__retake/:path*",
97
+ { source: "/((?!_next/static|_next/image).*)", has: [{ type: "header", key: "sec-fetch-mode", value: "navigate" }] },
98
+ // ...your entries
99
+ ],
100
+ }
101
+ ```
102
+
103
+ `withRetake` hands every request it doesn't answer to your function, so with a
104
+ merged matcher your function also sees page loads it didn't match before.
105
+ No install at all: `npx retake-dev .` runs `next dev` behind Retake's own port
106
+ (3014) instead.
107
+
54
108
  ### Connect your coding agent (MCP)
55
109
  Notes you leave in the dock can go straight to your coding agent. Register the
56
110
  MCP server once, from the app folder. With Claude Code:
@@ -63,10 +117,26 @@ Any other MCP client (Cursor, Codex, Windsurf...) takes the same command,
63
117
  `npx -y retake-dev mcp`, in its MCP settings.
64
118
 
65
119
  With the dev server running, the agent can `list_notes`, `get_note`,
66
- `get_active_timeline`, `acknowledge`, `resolve`, `reply` and `watch_notes`.
120
+ `get_moment`, `get_animation`, `get_timeline_events`, `get_active_timeline`,
121
+ `acknowledge`, `resolve`, `reply` and `watch_notes`.
67
122
  It finds the server through `.retake/server.json` (or pass `--url http://localhost:3014`).
68
123
  Acknowledging and resolving show up on the note in the dock right away.
69
124
 
125
+ A note is pinned to a moment of a recording. `get_moment` (a note id, or a
126
+ timeline and a time like `00:10.91`) reads that recording around it: the user's
127
+ clicks, keys, typing and route changes, the requests, the animations on or near
128
+ the element (start, end, duration, what they animate between, how far along at
129
+ the moment) and when the screen changed most. `get_timeline_events` lists the
130
+ same for any stretch of a timeline. A note whose source is only a line of a
131
+ compiled bundle (a Next.js chunk) is mapped back to the app's file and line
132
+ through the bundle's source map.
133
+
134
+ A note on an animation says exactly which part of it you mean (see
135
+ [Notes on animations](#notes-on-animations)). `get_animation` gives that
136
+ animation's timing and every keyframe, and maps any recording time onto its own
137
+ clock, in CSS %, Motion `times` and GSAP seconds. `get_note`, `get_moment` and
138
+ Copy for agent describe the element and its animation with the same words.
139
+
70
140
  ### The first 60 seconds
71
141
  1. **Use your app** for a few seconds: it's being recorded already.
72
142
  2. **Pause** with space (or ⌥P, or the play button). The app goes view-only:
@@ -81,13 +151,86 @@ Acknowledging and resolving show up on the note in the dock right away.
81
151
  the element, its React component, source file:line and CSS; or let your
82
152
  agent pick it up over MCP.
83
153
 
154
+ ### Picking an element
155
+ Hold ⌘ over the paused app: a chip by the pointer lists what's under it, and the
156
+ pick is what you can see there: text, an image or icon, a control, a painted box.
157
+ Empty overlays (a glow, a stretched card link, a scrim) and invisible layers
158
+ (opacity 0, a closed menu) stay in the list, dimmed with why, one wheel step or
159
+ Tab away. On an icon you get the `<svg>` (or the button it's the icon of), with
160
+ the shape inside it a step away; inside an open shadow root, the element itself;
161
+ an embedded iframe is one element (and gets no clicks while paused). A second
162
+ ⌘-click on a pinned spot makes another note there.
163
+
164
+ The note's selector finds the element again on a fresh load: no generated ids
165
+ (`:r1:`, `radix-…`), no classes that came and went during the recording (`in`,
166
+ `is-open`, `opacity-100`), test ids, labels and hrefs where they're unique. Its
167
+ source is where the element itself was written, a server component's line too
168
+ (the dev server reads the chunk's source map), never just the parent it was
169
+ passed into; Next's own layout components aren't listed as yours.
170
+
171
+ ### Notes on animations
172
+ Web animations aren't edited on a global timeline: each one belongs to an
173
+ element, runs on its own clock (0 is the end of its delay), and moves the
174
+ element between keyframes. Notes work the same way.
175
+
176
+ - **⌘-click an element** and it gets a row on the track under the timelines, with
177
+ its own animations (and an ancestor's that moves it). Nothing moving on it?
178
+ The row says so and lists what animates inside it; click one to switch. Two
179
+ animations at once (a transform and an opacity transition) stack in thinner
180
+ rows; hover one to see its name.
181
+ - **Click an animation** on that row to open it: a ruler on its own clock (the
182
+ delay hatched before 0), a diamond where each keyframe is reached, and small
183
+ lines of what it moves (x, y, scale, rotation, opacity, size). The hint reads
184
+ where the playhead is on it: `fadeUp · 100ms of 500 · 20% · seg 0→40% ease-out ·
185
+ x 248 y 568 · 320×64`. On the app, dashed boxes show where the element starts and
186
+ ends, and a dotted line the path it takes.
187
+ - **A point**: click on the open animation (or step with ←/→ a frame at a time,
188
+ Shift+←/→ keyframe to keyframe). The note is pinned there: "at 100ms (20%) of
189
+ fadeUp on `<h1.title>`".
190
+ - **A range**: drag across the open animation ("200–400ms · 40–80% of fadeUp").
191
+ It snaps to keyframes and 10ms steps (hold ⌥ to drag freely); drag past the
192
+ end for "from here to the end". Shift+drag on the timeline selects a range of
193
+ the recording first, then ⌘-click any element.
194
+ - **Typed numbers** ("at 100ms", "between 200 and 400 ms", "after 40%") are read
195
+ on the open animation's own clock; a chip under the note says how, and a click
196
+ switches it to recording time.
197
+ - Esc closes the open animation, then the note.
198
+
199
+ The note carries, for that animation: its kind and where it's defined, timing,
200
+ every keyframe, the point or both range edges on its own clock (local ms,
201
+ progress, eased progress, the keyframe segment), the values and the element's
202
+ box there and one frame either side, samples across a range, and what to change
203
+ for its kind (CSS keyframes, transitions, `element.animate()`, Motion, GSAP,
204
+ scroll-driven). It ends with an exact edit: the keyframes again on plain time,
205
+ the point or range edges as keyframes of their own, each piece keeping its part
206
+ of the curve, so an agent changes only the marked part and the rest moves as
207
+ before. A `@keyframes` shared with other elements is flagged, and the edit gives
208
+ the noted element its own copy. Motion written as inline styles every frame
209
+ (GSAP, Motion's `x`, react-spring) is recorded too, with its first and last
210
+ values and the library named; Motion keyframes come with every keyframe, their
211
+ `times` and each segment's ease, read off the component's props. A CSS
212
+ transition's exact edit is its timing as `linear()` stops.
213
+
214
+ ### The dock
215
+ - **Keys**: space or ⌥P plays and pauses, ←/→ step (on an open animation: its
216
+ frames; Shift: its keyframes), F fits everything, + starts a new timeline at
217
+ the playhead, M drops a bookmark, ⌥T folds the dock away, Esc closes an open
218
+ animation, a selected range, then the note.
219
+ - **Resize** by dragging the divider at its top. Drag it all the way down and
220
+ the timeline folds into a round button (bottom-right at first): the whole
221
+ window is the app's, and recording carries on. Drag the button anywhere; it
222
+ keeps to the nearer side edge. Click it (or ⌥T) to bring the dock back.
223
+ Folded or not, and where the button sits, are remembered across reloads.
224
+ - **Notes show when paused.** While the app is live or playing, note pins stay
225
+ off it (the count on the notes icon stays). Pause and the notes come back.
226
+
84
227
  ### Uninstall
85
228
  ```sh
86
229
  npm uninstall retake-dev # or pnpm remove / yarn remove / bun remove
87
230
  rm -rf .retake # Retake's session and recordings
88
231
  claude mcp remove retake # if you added the MCP server (or remove it in your client's MCP settings)
89
232
  ```
90
- Remove `retake()` from `vite.config` if you added it.
233
+ Remove `retake()` from `vite.config`, or Retake's `proxy.ts` / `middleware.ts` (or `withRetake`), if you added it.
91
234
 
92
235
  ## Commands
93
236
  ```sh
@@ -97,7 +240,7 @@ retake <project> --code-branches # each timeline keeps its own version of th
97
240
  retake <project> -- --host # anything after -- goes to the dev server
98
241
  retake -- <dev command> # run that command with the timeline in front (retake -- next dev)
99
242
  retake http://localhost:3000 # put the timeline in front of a dev server that's already running
100
- retake init # print the vite.config lines
243
+ retake init # print the vite.config / proxy.ts lines
101
244
  retake mcp # the MCP server your coding agent runs
102
245
  ```
103
246
  `--root <dir>` puts `.retake/` somewhere else; `--verbose` logs every request the
@@ -124,7 +267,7 @@ the package manager its lockfile names):
124
267
  | Framework | Run | Notes |
125
268
  |---|---|---|
126
269
  | Vite SPA (React, Vue, Svelte, plain) | `npx retake-dev .` | or `plugins: [retake()]` in `vite.config` |
127
- | Next.js | `npx retake-dev .` | tested on 16.3 (app and pages router, server actions) and 15.5 (app router), Turbopack |
270
+ | Next.js | `proxy.ts` / `middleware.ts` ([above](#nextjs-on-your-usual-dev-url)) and your usual `next dev`, or `npx retake-dev .` | tested on 16.3 (app and pages router, server actions) and 15.5 (app router), Turbopack and webpack |
128
271
  | React Router 7 framework mode | `npx retake-dev .` | or `plugins: [retake(), reactRouter()]` and your usual `npm run dev`; tested on 7.18 |
129
272
  | Remix 2 (Vite) | `npx retake-dev .` | tested on 2.17 |
130
273
  | Astro | `npx retake-dev .` | tested on 7.3 with React islands and `<ClientRouter />` |
@@ -136,9 +279,9 @@ the package manager its lockfile names):
136
279
 
137
280
  Tested means: the app hydrates in the dock with no warning, and recording, scrubbing back, the rebuilt moment, Play, hot
138
281
  updates and reloads all work, started either way (`retake .` or `retake http://localhost:…`). The untested ones go
139
- through the same front server and should work; say so if one doesn't.
282
+ through the same front server and should work; open an issue if one doesn't.
140
283
 
141
- - **Which port?** Retake sets `PORT` to a free port for the dev command (or keeps
284
+ - **Ports.** Retake sets `PORT` to a free port for the dev command (or keeps
142
285
  yours), and otherwise uses the first `http://localhost:…` the command prints, so
143
286
  tools that ignore `PORT` work too. Ctrl-C stops the dev server with it.
144
287
  - **The plugin in a Vite-based framework.** With `retake()` in `vite.config` and
@@ -159,10 +302,13 @@ through the same front server and should work; say so if one doesn't.
159
302
  loaded chunks) run at their recorded moment, the dev server's own traffic (HMR)
160
303
  is left out of the recording, and a rebuilt moment gets the page's HTML as it
161
304
  was recorded (kept in `.retake/docs/`), not rendered again. Native `import()`
162
- (Vite's lazy routes, Astro islands) can't be held to its moment.
163
- - **Bottom of the app hidden by the dock?** The dock floats over the bottom of
305
+ (Vite's lazy routes, Astro islands) can't be held to its moment. With Next's
306
+ `proxy.ts` the same holds, except that a rebuilt moment's page is rendered again
307
+ (what the server renders differently each time, like the time, can differ).
308
+ - **The dock covers the bottom of the app.** It floats over the bottom of
164
309
  the app (a cookie banner's buttons, Next's dev badge). Drag the dock's divider
165
- down, or open the app with `?retake=0`.
310
+ down (all the way down folds it into a button in the corner, as does ⌥T), or
311
+ open the app with `?retake=0`.
166
312
  - **Not rewound.** Retake rewinds the browser, not your server: database writes,
167
313
  server sessions and server-action side effects stay as they are (replays answer
168
314
  from the recording). Service workers are off while Retake is in front, and
@@ -173,6 +319,23 @@ you're on takes the new code; the others keep theirs. Stepping into a timeline
173
319
  checks its code out on disk (snapshots are kept in `.retake/`, and the newest
174
320
  code is put back when the server stops), but use it on prototypes, not shared repos.
175
321
 
322
+ ## The dock without a Retake server
323
+
324
+ `shellHtml(options)` (exported from `retake-dev`) returns the dock page, for a
325
+ host that serves it itself, such as a deployed demo. With no Retake server
326
+ behind it, pass `server: false`: the dock then never requests `/__retake/` (no
327
+ session fetch, so no 404 in the console) and keeps timelines and notes in memory
328
+ for the page.
329
+
330
+ ```js
331
+ import { shellHtml } from "retake-dev"
332
+ const html = shellHtml({ marker: "header", server: false })
333
+ ```
334
+
335
+ `markSvg()` returns Retake's mark (the folded button's icon) as an SVG string:
336
+ a 24×24 viewBox, the arc and the played part in `currentColor`, the rest white,
337
+ for a dark background.
338
+
176
339
  ## How going back works
177
340
 
178
341
  It doesn't snapshot the DOM. It records every input (pointer, keys, typing,
package/bin/retake.js CHANGED
@@ -38,7 +38,8 @@ Usage:
38
38
  retake http://localhost:3000 put the timeline in front of a running dev server
39
39
  retake -- <dev command> run that command with the timeline in front
40
40
  (e.g. retake -- next dev, retake -- pnpm dev)
41
- retake init print the vite.config lines instead
41
+ retake init print the lines for vite.config or Next's
42
+ proxy.ts instead
42
43
  retake mcp [--url http://localhost:3014]
43
44
  MCP server for coding agents (stdio)
44
45
 
@@ -289,6 +290,18 @@ function init() {
289
290
  })
290
291
 
291
292
  Options: retake({ codeBranches: true }) gives each timeline its own version of the code.
293
+
294
+ Next.js: one file in the project root (src/ if your app is in src/app), dev only; the timeline
295
+ shows on your usual dev URL:
296
+
297
+ // proxy.ts (Next 16)
298
+ export { default } from "retake-dev/next"
299
+
300
+ // middleware.ts (Next 15)
301
+ import retake from "retake-dev/next"
302
+ export default retake
303
+ export const config = { runtime: "nodejs" }
304
+
292
305
  Or leave the project untouched and run: npx retake-dev .
293
306
  (Next, Nuxt, React Router, SvelteKit, Astro... too: it puts Retake in front of your dev server.)`)
294
307
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retake-dev",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Retake: a time machine for your dev server (Vite, Next.js, React Router, Remix, Astro, SvelteKit, Nuxt). Drag back on a timeline and your app is at that moment; branch a new take from there.",
5
5
  "homepage": "https://retake-omega.vercel.app",
6
6
  "repository": {
@@ -21,6 +21,11 @@
21
21
  "./client": {
22
22
  "types": "./types/client.d.ts"
23
23
  },
24
+ "./next": {
25
+ "types": "./types/next.d.ts",
26
+ "default": "./src/next.js"
27
+ },
28
+ "./next-dev": "./src/next-dev.js",
24
29
  "./package.json": "./package.json"
25
30
  },
26
31
  "bin": {
@@ -33,6 +38,7 @@
33
38
  "!src/shell/mock",
34
39
  "types",
35
40
  "README.md",
41
+ "CHANGELOG.md",
36
42
  "LICENSE"
37
43
  ],
38
44
  "engines": {
package/src/core.js CHANGED
@@ -39,6 +39,11 @@ export function runtimeSource(rt = {}) {
39
39
  return `;(function () {\n"use strict";\nif (window.__retake) return;\nconst RT = Object.freeze(${config});\n${body}\n})();`
40
40
  }
41
41
 
42
+ // Retake's mark (src/mark.svg): a playhead on a track with an arc back to it.
43
+ // 24×24, the arc and the played part in `currentColor`, the rest white. The
44
+ // dock's folded button uses it; so can a favicon (on a dark tile).
45
+ export const markSvg = () => read("mark.svg").trim()
46
+
42
47
  /** @param {import("../types/index.js").ShellOptions} [options] */
43
48
  export function shellHtml(options = {}) {
44
49
  const config = {
@@ -48,19 +53,36 @@ export function shellHtml(options = {}) {
48
53
  ...(options.marker && options.marker !== "url" ? { marker: options.marker } : {}),
49
54
  // Behind the front server: rebuilds ask for the page as it was recorded (F56).
50
55
  ...(options.docs ? { docs: true } : {}),
56
+ // No Retake server behind it (a static or serverless deploy): the dock
57
+ // never asks /__retake/ for anything and keeps the session in memory.
58
+ ...(options.server === false ? { server: false } : {}),
59
+ // The project folder: source paths in notes are given relative to it.
60
+ ...(options.root ? { root: options.root } : {}),
51
61
  }
52
62
  return read("shell", "shell.html")
53
63
  .replace(
54
64
  "/*CONFIG*/",
55
65
  () => `window.__retakeConfig = ${JSON.stringify(config)};` + (options.token ? `window.__RETAKE_TOKEN = ${JSON.stringify(options.token)};` : ""),
56
66
  )
67
+ .replace("<!--MARK-->", () => markSvg().replace("<svg ", '<svg width="22" height="22" aria-hidden="true" '))
57
68
  .replace("/*CSS*/", () => read("shell", "shell.css"))
58
69
  .replace("/*JS*/", () => {
59
70
  const files = setFiles("shell")
60
- return `;(function () {\n"use strict";\n${files.map((f) => read("shell", f)).join("\n")}\n})();`
71
+ return `;(function () {\n"use strict";\n${noteTextSource()}\n${files.map((f) => read("shell", f)).join("\n")}\n})();`
61
72
  })
62
73
  }
63
74
 
75
+ // src/note-text.js (the note as text for an agent, shared with `retake mcp`)
76
+ // for the dock: its exports as one const, `NT`.
77
+ export function noteTextSource() {
78
+ const src = read("note-text.js")
79
+ const names = new Set()
80
+ const body = src
81
+ .replace(/^export (?:async )?(function|const|let) (\w+)/gm, (_, kind, name) => (names.add(name), `${kind} ${name}`))
82
+ .replace(/^export \{([^}]*)\}\s*$/gm, (_, list) => (list.split(",").forEach((n) => n.trim() && names.add(n.trim())), ""))
83
+ return `const NT = (function () {\n${body}\nreturn { ${[...names].join(", ")} }\n})();`
84
+ }
85
+
64
86
  // The script a server injects into a page it can't tell apart from the app's
65
87
  // own frames (any iframe document). It takes itself out of the DOM first (so an
66
88
  // app hydrating the whole document never sees it), then runs the runtime only
package/src/mark.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M3 17h18" stroke="#fff" stroke-opacity=".28" stroke-width="2"/><path d="M3 17h7" stroke="currentColor" stroke-width="2"/><path d="M19 14c0-5.5-9-7.5-9-1" stroke="currentColor" stroke-width="1.8"/><path d="M7.6 10.9 10 13.3l2.4-2.4" stroke="currentColor" stroke-width="1.8"/><circle cx="10" cy="17" r="2.6" fill="#fff"/></svg>