skillet-shared 0.1.0 → 0.2.0

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
@@ -11,4 +11,5 @@ Both apps run it through Vite, so imports keep their `.ts` form and need no buil
11
11
 
12
12
  - `pnpm test` and `pnpm typecheck` run the package's own suite
13
13
  - In an app, `pnpm link ../skillet-shared` tests an edit before it is tagged
14
- - Release: bump `version`, tag it, `npm publish`, then bump the range in each app
14
+ - Release: `pnpm version patch` (or `minor`). It tests, tags and pushes, and CI publishes to npm through trusted publishing
15
+ - Then bump the range in each app
@@ -0,0 +1,15 @@
1
+ export declare const HISTORY_MAX = 24;
2
+ export interface History<T> {
3
+ readonly past: readonly T[];
4
+ readonly future: readonly T[];
5
+ }
6
+ export declare const EMPTY_HISTORY: History<never>;
7
+ export declare function record<T>(h: History<T>, prev: T, same: (a: T, b: T) => boolean): History<T>;
8
+ export declare function stepBack<T>(h: History<T>, current: T): {
9
+ history: History<T>;
10
+ value: T;
11
+ } | null;
12
+ export declare function stepForward<T>(h: History<T>, current: T): {
13
+ history: History<T>;
14
+ value: T;
15
+ } | null;
@@ -0,0 +1,48 @@
1
+ // A bounded undo/redo walk over whole-board snapshots.
2
+ //
3
+ // One step back would be enough if every destructive move were deliberate. It
4
+ // stops being enough the moment finding a sound becomes a search: mutate,
5
+ // mutate, random, mutate, and the board worth keeping is three steps behind the
6
+ // one you can hear, with no way back to it. So the walk is retraceable in both
7
+ // directions — which is also what makes a wild jitter safe to try, since the
8
+ // cost of a bad roll is one keystroke rather than the board you had.
9
+ //
10
+ // Pure and generic on purpose: the interesting behaviour is the dedupe, the cap
11
+ // and when the redo tail dies, and all three are worth pinning down in a test.
12
+ // Deep enough to cover a run of rolls, small enough that a snapshot per entry
13
+ // stays free — a couple of hundred numbers each.
14
+ export const HISTORY_MAX = 24;
15
+ export const EMPTY_HISTORY = { past: [], future: [] };
16
+ // Record the state being replaced. `same` decides what counts as no movement:
17
+ // more than one path snapshots the same board on the way to a single change, and
18
+ // a stack of identical entries turns undo into a key you press four times to
19
+ // hear anything.
20
+ export function record(h, prev, same) {
21
+ const top = h.past.at(-1);
22
+ if (top !== undefined && same(top, prev)) {
23
+ // Still a new branch: the redo tail belonged to a walk this write leaves.
24
+ return h.future.length === 0 ? h : { past: h.past, future: [] };
25
+ }
26
+ return { past: [...h.past, prev].slice(-HISTORY_MAX), future: [] };
27
+ }
28
+ // Step back. `current` is the live state, which becomes the head of the redo
29
+ // tail — the caller owns it, so it is passed in rather than kept here and left
30
+ // to drift.
31
+ export function stepBack(h, current) {
32
+ const value = h.past.at(-1);
33
+ if (value === undefined)
34
+ return null;
35
+ return {
36
+ history: { past: h.past.slice(0, -1), future: [current, ...h.future] },
37
+ value,
38
+ };
39
+ }
40
+ export function stepForward(h, current) {
41
+ const [value, ...rest] = h.future;
42
+ if (value === undefined)
43
+ return null;
44
+ return {
45
+ history: { past: [...h.past, current].slice(-HISTORY_MAX), future: rest },
46
+ value,
47
+ };
48
+ }
@@ -0,0 +1,20 @@
1
+ import { z } from 'astro/zod';
2
+ export declare const postSchema: z.ZodObject<{
3
+ title: z.ZodString;
4
+ date: z.ZodCoercedDate<unknown>;
5
+ summary: z.ZodString;
6
+ image: z.ZodString;
7
+ imageAlt: z.ZodString;
8
+ look: z.ZodOptional<z.ZodString>;
9
+ draft: z.ZodDefault<z.ZodBoolean>;
10
+ }, z.core.$strip>;
11
+ export type PostData = z.infer<typeof postSchema>;
12
+ export declare const published: (post: {
13
+ data: PostData;
14
+ }) => boolean;
15
+ export declare const isoDay: (date: Date) => string;
16
+ export declare function longDate(date: Date): string;
17
+ export declare function newestFirst<T extends {
18
+ id: string;
19
+ data: PostData;
20
+ }>(posts: T[]): T[];
package/dist/posts.js ADDED
@@ -0,0 +1,25 @@
1
+ // The blog's posts are markdown files written by hand. `scripts/blogdraft.mjs`
2
+ // drafts one from the commits since the last post.
3
+ import { z } from 'astro/zod';
4
+ export const postSchema = z.object({
5
+ title: z.string(),
6
+ date: z.coerce.date(),
7
+ summary: z.string(),
8
+ image: z.string(),
9
+ imageAlt: z.string(),
10
+ look: z.string().optional(),
11
+ draft: z.boolean().default(false),
12
+ });
13
+ export const published = (post) => !post.data.draft;
14
+ export const isoDay = (date) => date.toISOString().slice(0, 10);
15
+ export function longDate(date) {
16
+ return date.toLocaleDateString('en-US', {
17
+ year: 'numeric',
18
+ month: 'long',
19
+ day: 'numeric',
20
+ timeZone: 'UTC',
21
+ });
22
+ }
23
+ export function newestFirst(posts) {
24
+ return posts.toSorted((a, b) => b.data.date.getTime() - a.data.date.getTime() || a.id.localeCompare(b.id));
25
+ }
@@ -0,0 +1 @@
1
+ export declare function sinceWords(at: number, now: number): string;
@@ -0,0 +1,30 @@
1
+ // How long ago something happened, in the words the home page puts on a resume
2
+ // card and under a saved voice. Coarse on purpose: the reader is picking which
3
+ // of their own boards to open, so "3 days ago" is the whole answer and a date
4
+ // is one more thing to read.
5
+ const MINUTE = 60_000;
6
+ const HOUR = 60 * MINUTE;
7
+ const DAY = 24 * HOUR;
8
+ const MONTH = 30 * DAY;
9
+ const YEAR = 365 * DAY;
10
+ const count = (ms, unit, name) => {
11
+ const n = Math.floor(ms / unit);
12
+ return `${n} ${name}${n === 1 ? '' : 's'} ago`;
13
+ };
14
+ // A clock that ran backwards — a machine whose time was wrong when the save
15
+ // landed, or two machines disagreeing — reads as "just now" rather than as a
16
+ // negative count.
17
+ export function sinceWords(at, now) {
18
+ const ms = now - at;
19
+ if (!Number.isFinite(ms) || ms < MINUTE)
20
+ return 'just now';
21
+ if (ms < HOUR)
22
+ return count(ms, MINUTE, 'minute');
23
+ if (ms < DAY)
24
+ return count(ms, HOUR, 'hour');
25
+ if (ms < MONTH)
26
+ return count(ms, DAY, 'day');
27
+ if (ms < YEAR)
28
+ return count(ms, MONTH, 'month');
29
+ return count(ms, YEAR, 'year');
30
+ }
package/package.json CHANGED
@@ -1,24 +1,40 @@
1
1
  {
2
2
  "name": "skillet-shared",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Code shared by bender and videoskillet",
5
5
  "license": "MIT",
6
6
  "author": "Colin Diesh",
7
7
  "repository": "github:cmdcolin/skillet-shared",
8
8
  "type": "module",
9
9
  "exports": {
10
- "./history": "./src/history.ts",
11
- "./relative-time": "./src/relative-time.ts",
12
- "./posts": "./src/posts.ts",
13
- "./tokens.css": "./src/tokens.css"
10
+ "./history": {
11
+ "types": "./dist/history.d.ts",
12
+ "default": "./dist/history.js"
13
+ },
14
+ "./relative-time": {
15
+ "types": "./dist/relative-time.d.ts",
16
+ "default": "./dist/relative-time.js"
17
+ },
18
+ "./posts": {
19
+ "types": "./dist/posts.d.ts",
20
+ "default": "./dist/posts.js"
21
+ },
22
+ "./tokens.css": "./dist/tokens.css"
14
23
  },
15
- "files": ["src", "!src/**/*.test.ts"],
24
+ "files": [
25
+ "dist"
26
+ ],
16
27
  "publishConfig": {
17
28
  "access": "public"
18
29
  },
30
+ "packageManager": "pnpm@11.20.0",
19
31
  "scripts": {
20
32
  "test": "vitest run",
21
- "typecheck": "tsc"
33
+ "typecheck": "tsc",
34
+ "build": "tsc -p tsconfig.build.json && cp src/tokens.css dist/",
35
+ "prepack": "pnpm build",
36
+ "preversion": "pnpm typecheck && pnpm test",
37
+ "postversion": "git push --follow-tags"
22
38
  },
23
39
  "peerDependencies": {
24
40
  "astro": "^7.4.2"
package/src/history.ts DELETED
@@ -1,68 +0,0 @@
1
- // A bounded undo/redo walk over whole-board snapshots.
2
- //
3
- // One step back would be enough if every destructive move were deliberate. It
4
- // stops being enough the moment finding a sound becomes a search: mutate,
5
- // mutate, random, mutate, and the board worth keeping is three steps behind the
6
- // one you can hear, with no way back to it. So the walk is retraceable in both
7
- // directions — which is also what makes a wild jitter safe to try, since the
8
- // cost of a bad roll is one keystroke rather than the board you had.
9
- //
10
- // Pure and generic on purpose: the interesting behaviour is the dedupe, the cap
11
- // and when the redo tail dies, and all three are worth pinning down in a test.
12
-
13
- // Deep enough to cover a run of rolls, small enough that a snapshot per entry
14
- // stays free — a couple of hundred numbers each.
15
- export const HISTORY_MAX = 24
16
-
17
- export interface History<T> {
18
- // Oldest first, so the most recent step back is the last element.
19
- readonly past: readonly T[]
20
- // Nearest first: the state undo just left, ready to be stepped back into.
21
- readonly future: readonly T[]
22
- }
23
-
24
- export const EMPTY_HISTORY: History<never> = { past: [], future: [] }
25
-
26
- // Record the state being replaced. `same` decides what counts as no movement:
27
- // more than one path snapshots the same board on the way to a single change, and
28
- // a stack of identical entries turns undo into a key you press four times to
29
- // hear anything.
30
- export function record<T>(
31
- h: History<T>,
32
- prev: T,
33
- same: (a: T, b: T) => boolean,
34
- ): History<T> {
35
- const top = h.past.at(-1)
36
- if (top !== undefined && same(top, prev)) {
37
- // Still a new branch: the redo tail belonged to a walk this write leaves.
38
- return h.future.length === 0 ? h : { past: h.past, future: [] }
39
- }
40
- return { past: [...h.past, prev].slice(-HISTORY_MAX), future: [] }
41
- }
42
-
43
- // Step back. `current` is the live state, which becomes the head of the redo
44
- // tail — the caller owns it, so it is passed in rather than kept here and left
45
- // to drift.
46
- export function stepBack<T>(
47
- h: History<T>,
48
- current: T,
49
- ): { history: History<T>; value: T } | null {
50
- const value = h.past.at(-1)
51
- if (value === undefined) return null
52
- return {
53
- history: { past: h.past.slice(0, -1), future: [current, ...h.future] },
54
- value,
55
- }
56
- }
57
-
58
- export function stepForward<T>(
59
- h: History<T>,
60
- current: T,
61
- ): { history: History<T>; value: T } | null {
62
- const [value, ...rest] = h.future
63
- if (value === undefined) return null
64
- return {
65
- history: { past: [...h.past, current].slice(-HISTORY_MAX), future: rest },
66
- value,
67
- }
68
- }
package/src/posts.ts DELETED
@@ -1,37 +0,0 @@
1
- // The blog's posts are markdown files written by hand. `scripts/blogdraft.mjs`
2
- // drafts one from the commits since the last post.
3
- import { z } from 'astro/zod'
4
-
5
- export const postSchema = z.object({
6
- title: z.string(),
7
- date: z.coerce.date(),
8
- summary: z.string(),
9
- image: z.string(),
10
- imageAlt: z.string(),
11
- look: z.string().optional(),
12
- draft: z.boolean().default(false),
13
- })
14
-
15
- export type PostData = z.infer<typeof postSchema>
16
-
17
- export const published = (post: { data: PostData }) => !post.data.draft
18
-
19
- export const isoDay = (date: Date) => date.toISOString().slice(0, 10)
20
-
21
- export function longDate(date: Date) {
22
- return date.toLocaleDateString('en-US', {
23
- year: 'numeric',
24
- month: 'long',
25
- day: 'numeric',
26
- timeZone: 'UTC',
27
- })
28
- }
29
-
30
- export function newestFirst<T extends { id: string; data: PostData }>(
31
- posts: T[],
32
- ) {
33
- return posts.toSorted(
34
- (a, b) =>
35
- b.data.date.getTime() - a.data.date.getTime() || a.id.localeCompare(b.id),
36
- )
37
- }
@@ -1,27 +0,0 @@
1
- // How long ago something happened, in the words the home page puts on a resume
2
- // card and under a saved voice. Coarse on purpose: the reader is picking which
3
- // of their own boards to open, so "3 days ago" is the whole answer and a date
4
- // is one more thing to read.
5
- const MINUTE = 60_000
6
- const HOUR = 60 * MINUTE
7
- const DAY = 24 * HOUR
8
- const MONTH = 30 * DAY
9
- const YEAR = 365 * DAY
10
-
11
- const count = (ms: number, unit: number, name: string): string => {
12
- const n = Math.floor(ms / unit)
13
- return `${n} ${name}${n === 1 ? '' : 's'} ago`
14
- }
15
-
16
- // A clock that ran backwards — a machine whose time was wrong when the save
17
- // landed, or two machines disagreeing — reads as "just now" rather than as a
18
- // negative count.
19
- export function sinceWords(at: number, now: number): string {
20
- const ms = now - at
21
- if (!Number.isFinite(ms) || ms < MINUTE) return 'just now'
22
- if (ms < HOUR) return count(ms, MINUTE, 'minute')
23
- if (ms < DAY) return count(ms, HOUR, 'hour')
24
- if (ms < MONTH) return count(ms, DAY, 'day')
25
- if (ms < YEAR) return count(ms, MONTH, 'month')
26
- return count(ms, YEAR, 'year')
27
- }
File without changes