@crup/react-timer-hook 0.0.1-alpha.3 โ†’ 0.0.1-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,79 +1,81 @@
1
1
  # @crup/react-timer-hook
2
2
 
3
- A small React hook library for deterministic timer lifecycles.
3
+ > Deterministic React timer primitives for countdowns, stopwatches, clocks, schedules, and many independent timers.
4
4
 
5
- This package is planned as a replacement for the previous `react-timer-hook` API. It intentionally does not ship formatting, timezone conversion, `ampm` fields, or countdown/stopwatch/clock mode enums. It gives you raw time data and lifecycle controls so your app can decide what the timer means.
5
+ [![npm alpha](https://img.shields.io/npm/v/%40crup%2Freact-timer-hook/alpha?label=npm%20alpha&color=00b894)](https://www.npmjs.com/package/@crup/react-timer-hook?activeTab=versions)
6
+ [![npm downloads](https://img.shields.io/npm/dm/%40crup%2Freact-timer-hook?color=0f766e)](https://www.npmjs.com/package/@crup/react-timer-hook)
7
+ [![CI](https://github.com/crup/react-timer-hook/actions/workflows/ci.yml/badge.svg)](https://github.com/crup/react-timer-hook/actions/workflows/ci.yml)
8
+ [![Docs](https://github.com/crup/react-timer-hook/actions/workflows/docs.yml/badge.svg)](https://github.com/crup/react-timer-hook/actions/workflows/docs.yml)
9
+ [![Size](https://github.com/crup/react-timer-hook/actions/workflows/size.yml/badge.svg)](https://github.com/crup/react-timer-hook/actions/workflows/size.yml)
10
+ [![license](https://img.shields.io/npm/l/%40crup%2Freact-timer-hook?color=111827)](./LICENSE)
11
+ [![types](https://img.shields.io/npm/types/%40crup%2Freact-timer-hook?color=2563eb)](./dist/index.d.ts)
6
12
 
7
- ## Status
13
+ ๐Ÿ“š Docs: https://crup.github.io/react-timer-hook/
8
14
 
9
- Alpha-ready v1 implementation. The package is intended to be published under the `@crup` npm scope.
15
+ ## Docs and live examples
10
16
 
11
- ## Install
12
-
13
- ```sh
14
- pnpm add @crup/react-timer-hook
15
- npm install @crup/react-timer-hook
16
- ```
17
+ The documentation site is built with Docusaurus and includes live React playgrounds for 15 recipes:
17
18
 
18
- ## Planned Public API
19
+ - Basic: clock, stopwatch, absolute countdown, pausable countdown, manual controls
20
+ - Intermediate: once-only `onEnd`, polling, poll-and-cancel, backend events, debug logs
21
+ - Advanced: many display timers, timer groups, global controls, per-item polling, dynamic items
19
22
 
20
- ```ts
21
- import { useTimer, useTimerGroup, durationParts } from '@crup/react-timer-hook';
22
- ```
23
+ Open: https://crup.github.io/react-timer-hook/
23
24
 
24
- V1 should expose only:
25
+ ## Why it is different
25
26
 
26
- - `useTimer()` for one timer lifecycle.
27
- - `useTimerGroup()` for many keyed independent timer lifecycles.
28
- - `durationParts()` as a pure numeric helper.
27
+ Most timer libraries mix scheduling, lifecycle, formatting, and app behavior. This package keeps the core small:
29
28
 
30
- ## Why This Shape
29
+ - โฑ๏ธ `useTimer()` for one lifecycle.
30
+ - ๐Ÿงญ `useTimerGroup()` for many keyed lifecycles with one shared scheduler.
31
+ - ๐Ÿงฉ `durationParts()` for display-friendly duration math.
32
+ - ๐Ÿงผ No timezone, locale, or formatting opinions.
33
+ - ๐Ÿงช Built around React Strict Mode, rerenders, async callbacks, and cleanup.
34
+ - ๐Ÿค– AI-friendly docs via `llms.txt`, `llms-full.txt`, and a tiny local MCP docs utility.
31
35
 
32
- Most timer libraries mix three concerns:
36
+ ## Install
33
37
 
34
- - scheduling
35
- - lifecycle state
36
- - presentation formatting
38
+ Alpha is the only intended release channel until stable publishing is explicitly unlocked.
37
39
 
38
- This library should only own scheduling and lifecycle mechanics.
40
+ ```sh
41
+ npm install @crup/react-timer-hook@alpha
42
+ pnpm add @crup/react-timer-hook@alpha
43
+ ```
39
44
 
40
- Consumers own:
45
+ ```ts
46
+ import { durationParts, useTimer, useTimerGroup } from '@crup/react-timer-hook';
47
+ ```
41
48
 
42
- - countdown math
43
- - stopwatch display
44
- - clock formatting
45
- - timezone and locale behavior
46
- - API polling behavior
47
- - audio or notification side effects
49
+ ## Quick examples
48
50
 
49
- ## Single Timer
51
+ ### Stopwatch
50
52
 
51
53
  ```tsx
52
- function Stopwatch() {
53
- const timer = useTimer({
54
- autoStart: false,
55
- updateIntervalMs: 100,
56
- });
54
+ import { useTimer } from '@crup/react-timer-hook';
55
+
56
+ export function Stopwatch() {
57
+ const timer = useTimer({ updateIntervalMs: 100 });
57
58
 
58
59
  return (
59
60
  <>
60
- <span>{Math.floor(timer.elapsedMilliseconds / 1000)}s</span>
61
+ <output>{Math.floor(timer.elapsedMilliseconds / 1000)}s</output>
61
62
  <button onClick={timer.start}>Start</button>
62
63
  <button onClick={timer.pause}>Pause</button>
63
64
  <button onClick={timer.resume}>Resume</button>
64
65
  <button onClick={timer.restart}>Restart</button>
65
- <button onClick={() => timer.reset()}>Reset</button>
66
66
  </>
67
67
  );
68
68
  }
69
69
  ```
70
70
 
71
- ## Absolute Deadline Countdown
71
+ ### Absolute countdown
72
72
 
73
- Use this for auctions, server deadlines, reservations, or any timer where the end timestamp comes from outside the UI.
73
+ Use `now` for wall-clock deadlines from a server, auction, reservation, or job expiry.
74
74
 
75
75
  ```tsx
76
- function AuctionTimer({ auctionId, expiresAt }: {
76
+ import { useTimer } from '@crup/react-timer-hook';
77
+
78
+ export function AuctionTimer({ auctionId, expiresAt }: {
77
79
  auctionId: string;
78
80
  expiresAt: number;
79
81
  }) {
@@ -86,188 +88,118 @@ function AuctionTimer({ auctionId, expiresAt }: {
86
88
 
87
89
  const remainingMs = Math.max(0, expiresAt - timer.now);
88
90
 
89
- if (timer.isEnded) {
90
- return <span>Auction ended</span>;
91
- }
92
-
91
+ if (timer.isEnded) return <span>Auction ended</span>;
93
92
  return <span>{Math.ceil(remainingMs / 1000)}s left</span>;
94
93
  }
95
94
  ```
96
95
 
97
- For absolute deadlines, `pause()` pauses the local timer lifecycle and schedules, but it does not change the external server deadline. On `resume()`, the next `now` value catches up to wall time.
96
+ ### Polling with early cancel
98
97
 
99
- ## Pausable Duration Countdown
100
-
101
- Use this when pausing should freeze the remaining duration.
98
+ Schedules run while the timer is active. Slow async work is skipped by default with `overlap: 'skip'`.
102
99
 
103
100
  ```tsx
104
- function BreakTimer() {
105
- const durationMs = 5 * 60 * 1000;
106
-
107
- const timer = useTimer({
108
- autoStart: true,
109
- updateIntervalMs: 1000,
110
- endWhen: snapshot => snapshot.elapsedMilliseconds >= durationMs,
111
- });
112
-
113
- const remainingMs = Math.max(0, durationMs - timer.elapsedMilliseconds);
114
-
115
- return <span>{Math.ceil(remainingMs / 1000)}s left</span>;
116
- }
101
+ const timer = useTimer({
102
+ autoStart: true,
103
+ updateIntervalMs: 1000,
104
+ endWhen: snapshot => snapshot.now >= expiresAt,
105
+ schedules: [
106
+ {
107
+ id: 'auction-poll',
108
+ everyMs: 5000,
109
+ overlap: 'skip',
110
+ callback: async (_snapshot, controls) => {
111
+ const auction = await api.getAuction(auctionId);
112
+ if (auction.status === 'sold') controls.cancel('sold');
113
+ },
114
+ },
115
+ ],
116
+ });
117
117
  ```
118
118
 
119
- ## Clock
119
+ ### Many independent timers
120
+
121
+ Use `useTimerGroup()` when every row needs its own pause, resume, cancel, restart, schedules, or `onEnd`.
120
122
 
121
123
  ```tsx
122
- function Clock() {
123
- const timer = useTimer({
124
+ const timers = useTimerGroup({
125
+ updateIntervalMs: 1000,
126
+ items: auctions.map(auction => ({
127
+ id: auction.id,
124
128
  autoStart: true,
125
- updateIntervalMs: 1000,
126
- });
127
-
128
- return <span>{new Date(timer.now).toLocaleTimeString()}</span>;
129
- }
129
+ endWhen: snapshot => snapshot.now >= auction.expiresAt,
130
+ onEnd: () => api.closeAuction(auction.id),
131
+ })),
132
+ });
130
133
  ```
131
134
 
132
- The hook does not format time. Use native `Intl`, `Date`, or your preferred date library.
133
-
134
- ## Schedules and Polling
135
+ ## Bundle size
135
136
 
136
- Schedules are optional side effects that run while a timer is active. They are useful for polling, audio cues, analytics pings, or other app-owned side effects.
137
+ Current local build:
137
138
 
138
- ```tsx
139
- function AuctionTimer({ auctionId, expiresAt }: Props) {
140
- const timer = useTimer({
141
- autoStart: true,
142
- updateIntervalMs: 1000,
143
- endWhen: snapshot => snapshot.now >= expiresAt,
144
- onEnd: () => api.closeAuction(auctionId),
145
- schedules: [
146
- {
147
- id: 'poll-auction',
148
- everyMs: 5000,
149
- overlap: 'skip',
150
- callback: async (_snapshot, controls) => {
151
- const auction = await api.getAuction(auctionId);
152
-
153
- if (auction.status === 'sold') {
154
- controls.cancel('sold');
155
- }
156
- },
157
- },
158
- ],
159
- });
139
+ | File | Raw | Gzip | Brotli |
140
+ | --- | ---: | ---: | ---: |
141
+ | `dist/index.js` | 27.32 kB | 4.69 kB | 4.18 kB |
142
+ | `dist/index.cjs` | 29.18 kB | 5.08 kB | 4.50 kB |
143
+ | `dist/index.d.ts` | 3.95 kB | 992 B | 888 B |
160
144
 
161
- if (timer.isCancelled) {
162
- return <span>Auction closed</span>;
163
- }
145
+ CI writes a size summary to the GitHub Actions UI and posts a bundle-size comment on pull requests.
164
146
 
165
- const remainingMs = Math.max(0, expiresAt - timer.now);
166
- return <span>{Math.ceil(remainingMs / 1000)}s left</span>;
167
- }
168
- ```
147
+ ## AI-friendly
169
148
 
170
- `overlap: 'skip'` is the default because it prevents slow async callbacks from piling up.
149
+ End users do not need these files. They are for coding agents, docs-aware IDEs, and MCP clients.
171
150
 
172
- ## Many Independent Timers
151
+ ### MCP setup
173
152
 
174
- For lists where each item has its own pause, resume, cancel, and end lifecycle, use `useTimerGroup`.
153
+ Clone the repo, install dependencies, and point your MCP client at the local stdio server:
175
154
 
176
- ```tsx
177
- function AuctionList({ auctions }: { auctions: Auction[] }) {
178
- const timers = useTimerGroup({
179
- updateIntervalMs: 1000,
180
- items: auctions.map(auction => ({
181
- id: auction.id,
182
- autoStart: true,
183
- endWhen: snapshot => snapshot.now >= auction.expiresAt,
184
- onEnd: () => api.closeAuction(auction.id),
185
- })),
186
- });
155
+ ```sh
156
+ git clone https://github.com/crup/react-timer-hook.git
157
+ cd react-timer-hook
158
+ pnpm install
159
+ ```
187
160
 
188
- return (
189
- <>
190
- {auctions.map(auction => {
191
- const timer = timers.get(auction.id);
192
- const remainingMs = Math.max(0, auction.expiresAt - (timer?.now ?? timers.now));
193
-
194
- return (
195
- <AuctionRow
196
- key={auction.id}
197
- auction={auction}
198
- remainingMs={remainingMs}
199
- isPaused={timer?.isPaused ?? false}
200
- isEnded={timer?.isEnded ?? false}
201
- onPause={() => timers.pause(auction.id)}
202
- onResume={() => timers.resume(auction.id)}
203
- onCancel={() => timers.cancel(auction.id, 'sold')}
204
- />
205
- );
206
- })}
207
- </>
208
- );
161
+ ```json
162
+ {
163
+ "mcpServers": {
164
+ "react-timer-hook-docs": {
165
+ "command": "node",
166
+ "args": ["/absolute/path/to/react-timer-hook/mcp/server.mjs"]
167
+ }
168
+ }
209
169
  }
210
170
  ```
211
171
 
212
- `useTimerGroup()` should use one scheduler internally, not one timeout loop per item.
213
-
214
- ## Debug Logs
215
-
216
- Debug logging is planned for v1, but it is opt-in.
172
+ The MCP server exposes:
217
173
 
218
- ```tsx
219
- const timer = useTimer({
220
- autoStart: true,
221
- updateIntervalMs: 1000,
222
- debug: event => {
223
- console.debug('[timer]', event);
224
- },
225
- });
174
+ ```txt
175
+ react-timer-hook://package
176
+ react-timer-hook://api
177
+ react-timer-hook://recipes
226
178
  ```
227
179
 
228
- No logs should be emitted by default.
180
+ Agents can use hosted context:
229
181
 
230
- Debug events should be semantic, for example `timer:start`, `timer:tick`, `scheduler:start`, `schedule:skip`, and `timer:end`. The library should not expose raw `setTimeout` handles.
182
+ - https://crup.github.io/react-timer-hook/llms.txt
183
+ - https://crup.github.io/react-timer-hook/llms-full.txt
231
184
 
232
- ## Bundle Size
185
+ Local MCP/docs helpers:
233
186
 
234
- Current local build size:
187
+ ```sh
188
+ pnpm ai:context
189
+ pnpm mcp:docs
190
+ ```
235
191
 
236
- | File | Raw | Gzip | Brotli |
237
- | --- | ---: | ---: | ---: |
238
- | `dist/index.js` | 27.32 kB | 4.69 kB | 4.18 kB |
239
- | `dist/index.cjs` | 29.18 kB | 5.08 kB | 4.50 kB |
240
- | `dist/index.d.ts` | 3.95 kB | 992 B | 888 B |
192
+ The MCP utility is repo-local and excluded from the published npm package.
241
193
 
242
- Run this after `pnpm build`:
194
+ ## Release policy
243
195
 
244
- ```sh
245
- pnpm size
246
- ```
196
+ - Published versions must stay `0.0.1-alpha.x` until stable release is explicitly unlocked.
197
+ - `@alpha` is the documented install tag right now.
198
+ - Npm requires a `latest` dist-tag, so the workflow keeps `latest` pointing at the current alpha until stable publishing is unlocked.
199
+
200
+ ## Links
247
201
 
248
- CI compares PR bundle size against `main` and writes a size summary to the workflow output.
249
-
250
- ## Implementation Notes
251
-
252
- - Use recursive `setTimeout`, not `setInterval`.
253
- - Never schedule timers during render.
254
- - Use `Date.now()` for wall-clock `now`.
255
- - Use `performance.now()` internally for active elapsed duration, with a `Date.now()` fallback.
256
- - Keep controls stable for React dependency arrays.
257
- - Keep latest callbacks and options in refs so rerenders do not restart the scheduler unnecessarily.
258
- - Guard async work with generation IDs.
259
- - Clean up on unmount.
260
- - Test with fake timers and React Strict Mode.
261
-
262
- See:
263
-
264
- - [Requirements](./REQUIREMENTS.md)
265
- - [API Specification](./docs/API.md)
266
- - [Design Decisions](./docs/DECISIONS.md)
267
- - [Recipes](./docs/RECIPES.md)
268
- - [Branching and Commits](./docs/BRANCHING_AND_COMMITS.md)
269
- - [Implementation Plan](./docs/IMPLEMENTATION.md)
270
- - [Task Plan](./docs/TASKS.md)
271
- - [OSS and GTM Plan](./docs/OSS_GTM.md)
272
- - [Release and Docs Plan](./docs/RELEASE_AND_DOCS.md)
273
- - [Agent Task Cards](./docs/AGENT_TASKS.md)
202
+ - ๐Ÿ“š Docs: https://crup.github.io/react-timer-hook/
203
+ - ๐Ÿ“ฆ npm: https://www.npmjs.com/package/@crup/react-timer-hook
204
+ - ๐Ÿงต Issues: https://github.com/crup/react-timer-hook/issues
205
+ - ๐Ÿค Contributing: ./CONTRIBUTING.md