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 +125 -0
- package/README.md +173 -10
- package/bin/retake.js +14 -1
- package/package.json +7 -1
- package/src/core.js +23 -1
- package/src/mark.svg +1 -0
- package/src/next-dev.js +183 -0
- package/src/next.js +81 -0
- package/src/note-text.js +844 -0
- package/src/plugin.js +7 -3
- package/src/runtime/10-animations.js +9 -0
- package/src/runtime/60-preview.js +3 -1
- package/src/runtime/62-motion.js +106 -0
- package/src/runtime/65-timeline.js +39 -0
- package/src/runtime/67-csssource.js +73 -1
- package/src/runtime/70-boot.js +28 -2
- package/src/server/api.js +73 -1
- package/src/server/front.js +3 -1
- package/src/server/mcp.js +225 -19
- package/src/server/moments.js +317 -0
- package/src/server/sourcemap.js +192 -0
- package/src/shell/00-state.js +7 -0
- package/src/shell/10-dock.js +208 -13
- package/src/shell/15-session.js +66 -10
- package/src/shell/20-timeline.js +47 -16
- package/src/shell/25-input.js +29 -2
- package/src/shell/30-notes.js +709 -113
- package/src/shell/32-anims.js +1353 -0
- package/src/shell/shell.css +79 -8
- package/src/shell/shell.html +10 -3
- package/types/client.d.ts +16 -0
- package/types/index.d.ts +16 -0
- package/types/next.d.ts +50 -0
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
|
|
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
|
-
`
|
|
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;
|
|
282
|
+
through the same front server and should work; open an issue if one doesn't.
|
|
140
283
|
|
|
141
|
-
- **
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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>
|