@athrio/loom 0.0.9 → 0.0.10

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.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { i as sqliteStore, n as localStore, r as notesFile, t as NoteStore } from "./store-DgMmJ7v9.js";
3
+ export { NoteStore, localStore, notesFile, sqliteStore };
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+ import { Bs as gen, Fn as Union, Gc as tap, Gp as fromNullishOr, Ha as publish, Hl as provide, Il as effect, Ka as unbounded, Kl as unwrap, Ms as forEach, Mt as Boolean, Os as flatMap, Qs as map, Tl as currentTimeMillis, Vc as succeed, Wc as sync, Wn as decodeUnknownEffect, Xf as head, Ys as logWarning, Zc as try_, _f as Service, al as void_, ec as mapError, em as match, fc as orDie, hs as catchTag, ii as tag, is as andThen, kl as TaggedError, ls as asVoid, mc as promise, n as SqlClient, on as Number, qc as tapError, qf as getSomes, rm as some, tm as none, vn as String, yn as Struct } from "./SqlClient-BHo_uVyk.js";
3
+ import { b as when, g as exhaustive, y as value } from "./dist-CKhPOkpR.js";
4
+ import { mkdirSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { dirname, join } from "node:path";
7
+ //#region ../loom-design/src/note.ts
8
+ var noteFields = {
9
+ project: String,
10
+ route: String,
11
+ text: String
12
+ };
13
+ var RectSchema = Struct({
14
+ x: Number,
15
+ y: Number,
16
+ width: Number,
17
+ height: Number
18
+ });
19
+ var elementFields = {
20
+ ...noteFields,
21
+ label: String,
22
+ rect: RectSchema
23
+ };
24
+ var domFields = {
25
+ kind: tag("dom"),
26
+ ...elementFields,
27
+ selector: String
28
+ };
29
+ var LoomSourceSchema = Struct({
30
+ chapter: String,
31
+ section: String
32
+ });
33
+ var loomFields = {
34
+ kind: tag("loom"),
35
+ ...elementFields,
36
+ source: LoomSourceSchema
37
+ };
38
+ var chatFields = {
39
+ kind: tag("chat"),
40
+ ...noteFields
41
+ };
42
+ var DraftSchema = Union([
43
+ Struct(domFields),
44
+ Struct(loomFields),
45
+ Struct(chatFields)
46
+ ]);
47
+ var stamp = {
48
+ seq: Number,
49
+ at: String,
50
+ addressed: Boolean
51
+ };
52
+ var DomNoteSchema = Struct({
53
+ ...domFields,
54
+ ...stamp
55
+ });
56
+ var LoomNoteSchema = Struct({
57
+ ...loomFields,
58
+ ...stamp
59
+ });
60
+ var ChatNoteSchema = Struct({
61
+ ...chatFields,
62
+ ...stamp
63
+ });
64
+ var NoteSchema = Union([
65
+ DomNoteSchema,
66
+ LoomNoteSchema,
67
+ ChatNoteSchema
68
+ ]);
69
+ var stampDraft = (draft, seq, at) => value(draft).pipe(when({ kind: "dom" }, (annotation) => DomNoteSchema.make({
70
+ ...annotation,
71
+ seq,
72
+ at,
73
+ addressed: false
74
+ })), when({ kind: "loom" }, (annotation) => LoomNoteSchema.make({
75
+ ...annotation,
76
+ seq,
77
+ at,
78
+ addressed: false
79
+ })), when({ kind: "chat" }, (message) => ChatNoteSchema.make({
80
+ ...message,
81
+ seq,
82
+ at,
83
+ addressed: false
84
+ })), exhaustive);
85
+ //#endregion
86
+ //#region ../loom-design/src/store.ts
87
+ var DecodeError = class extends TaggedError("DecodeError") {};
88
+ var migrations = [
89
+ {
90
+ id: 1,
91
+ name: "initial",
92
+ run: (sql) => gen(function* () {
93
+ yield* sql`CREATE TABLE IF NOT EXISTS notes (
94
+ project TEXT NOT NULL,
95
+ seq INTEGER NOT NULL,
96
+ data TEXT NOT NULL,
97
+ PRIMARY KEY (project, seq)
98
+ )`;
99
+ yield* sql`CREATE TABLE IF NOT EXISTS projects (
100
+ id TEXT PRIMARY KEY,
101
+ name TEXT NOT NULL
102
+ )`;
103
+ })
104
+ },
105
+ {
106
+ id: 2,
107
+ name: "project directory and address",
108
+ run: (sql) => gen(function* () {
109
+ yield* sql`ALTER TABLE projects ADD COLUMN directory TEXT`;
110
+ yield* sql`ALTER TABLE projects ADD COLUMN address TEXT`;
111
+ })
112
+ },
113
+ {
114
+ id: 3,
115
+ name: "project identity",
116
+ run: (sql) => sql`ALTER TABLE projects ADD COLUMN identity TEXT`.pipe(asVoid)
117
+ }
118
+ ];
119
+ var migrate = (sql) => gen(function* () {
120
+ yield* sql`CREATE TABLE IF NOT EXISTS _migrations (
121
+ id INTEGER PRIMARY KEY,
122
+ name TEXT NOT NULL,
123
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
124
+ )`;
125
+ const applied = yield* sql`SELECT id FROM _migrations`;
126
+ const done = new Set(applied.map((row) => row.id));
127
+ yield* forEach(migrations.filter((migration) => !done.has(migration.id)), (migration) => sql.withTransaction(migration.run(sql).pipe(andThen(sql`INSERT INTO _migrations (id, name) VALUES (${migration.id}, ${migration.name})`))), { discard: true });
128
+ });
129
+ var decodeNote = (data) => try_({
130
+ try: () => JSON.parse(data),
131
+ catch: (cause) => new DecodeError({
132
+ data,
133
+ cause
134
+ })
135
+ }).pipe(flatMap((json) => decodeUnknownEffect(NoteSchema)(json).pipe(mapError((cause) => new DecodeError({
136
+ data,
137
+ cause
138
+ })))));
139
+ var readNotes = (sql, project) => sql`
140
+ SELECT data FROM notes WHERE project = ${project} ORDER BY seq
141
+ `.pipe(flatMap((rows) => forEach(rows, (row) => decodeNote(row.data).pipe(tapError((error) => logWarning("skipping a note that no longer decodes", error)), map(some), catchTag("DecodeError", () => succeed(none()))))), map(getSomes));
142
+ var recordDraft = (sql, draft) => sql.withTransaction(gen(function* () {
143
+ const millis = yield* currentTimeMillis;
144
+ const note = stampDraft(draft, match(fromNullishOr((yield* sql`
145
+ SELECT MAX(seq) AS max FROM notes WHERE project = ${draft.project}
146
+ `)[0]?.max), {
147
+ onSome: (seq) => seq,
148
+ onNone: () => 0
149
+ }) + 1, new Date(millis).toISOString());
150
+ yield* sql`
151
+ INSERT INTO notes (project, seq, data)
152
+ VALUES (${note.project}, ${note.seq}, ${JSON.stringify(note)})
153
+ `;
154
+ return note;
155
+ }));
156
+ var modifyNote = (sql, project, seq, change) => gen(function* () {
157
+ yield* match(fromNullishOr((yield* sql`
158
+ SELECT data FROM notes WHERE project = ${project} AND seq = ${seq}
159
+ `)[0]), {
160
+ onNone: () => void_,
161
+ onSome: (row) => decodeNote(row.data).pipe(map(change), flatMap((updated) => sql`
162
+ UPDATE notes SET data = ${JSON.stringify(updated)}
163
+ WHERE project = ${project} AND seq = ${seq}
164
+ `))
165
+ });
166
+ });
167
+ var resolveNote = (sql, project, seq) => modifyNote(sql, project, seq, (note) => ({
168
+ ...note,
169
+ addressed: true
170
+ }));
171
+ var editNote = (sql, project, seq, text) => modifyNote(sql, project, seq, (note) => ({
172
+ ...note,
173
+ text
174
+ }));
175
+ var discardNote = (sql, project, seq) => sql`DELETE FROM notes WHERE project = ${project} AND seq = ${seq}`.pipe(asVoid);
176
+ var listProjects = (sql) => sql`SELECT id, name, directory, address, identity FROM projects ORDER BY name`;
177
+ var findProject = (sql, name) => sql`
178
+ SELECT id, name, directory, address, identity FROM projects WHERE id = ${name}
179
+ `.pipe(map(head));
180
+ var registerProject = (sql, name, identity, directory, address) => sql`
181
+ INSERT INTO projects (id, name, directory, address, identity)
182
+ VALUES (${name}, ${name}, ${directory}, ${address}, ${identity})
183
+ ON CONFLICT (id) DO UPDATE SET
184
+ directory = ${directory}, address = ${address}, identity = ${identity}
185
+ `.pipe(asVoid);
186
+ var ensureProject = (sql, id) => sql`
187
+ INSERT INTO projects (id, name) VALUES (${id}, ${id})
188
+ ON CONFLICT (id) DO NOTHING
189
+ `.pipe(asVoid);
190
+ var backfillProjects = (sql) => sql`
191
+ INSERT INTO projects (id, name)
192
+ SELECT DISTINCT project, project FROM notes
193
+ WHERE true
194
+ ON CONFLICT (id) DO NOTHING
195
+ `.pipe(asVoid);
196
+ var sqliteClientLayer = (filename) => unwrap(promise(() => typeof globalThis.Bun !== "undefined" ? import("./dist-kscTHM4c.js").then((m) => m.SqliteClient.layer({
197
+ filename,
198
+ create: true
199
+ })) : import("./dist-3ptbWhlX.js").then((m) => m.SqliteClient.layer({ filename }))));
200
+ var sqliteStore = (filename) => effect(NoteStore, NoteStore.make).pipe(provide(sqliteClientLayer(filename)));
201
+ var notesFile = () => process.env.LOOM_NOTES_DB ?? join(homedir(), ".loom", "notes.sqlite");
202
+ var localStore = () => unwrap(sync(() => {
203
+ const file = notesFile();
204
+ mkdirSync(dirname(file), { recursive: true });
205
+ return sqliteStore(file);
206
+ }));
207
+ var NoteStore = class extends Service()("NoteStore", { make: gen(function* () {
208
+ const sql = yield* SqlClient;
209
+ yield* migrate(sql);
210
+ yield* backfillProjects(sql);
211
+ const changes = yield* unbounded();
212
+ return {
213
+ list: (project) => readNotes(sql, project),
214
+ record: (draft) => ensureProject(sql, draft.project).pipe(andThen(recordDraft(sql, draft)), tap((note) => publish(changes, note))),
215
+ resolve: (project, seq) => resolveNote(sql, project, seq),
216
+ edit: (project, seq, text) => editNote(sql, project, seq, text),
217
+ discard: (project, seq) => discardNote(sql, project, seq),
218
+ projects: listProjects(sql),
219
+ project: (name) => findProject(sql, name),
220
+ register: (name, identity, directory, address) => registerProject(sql, name, identity, directory, address),
221
+ changes
222
+ };
223
+ }).pipe(orDie) }) {
224
+ static layer = localStore();
225
+ };
226
+ //#endregion
227
+ export { DraftSchema as a, sqliteStore as i, localStore as n, NoteSchema as o, notesFile as r, NoteStore as t };
228
+
229
+ //# sourceMappingURL=store-DgMmJ7v9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-DgMmJ7v9.js","names":[],"sources":["../../loom-design/src/note.ts","../../loom-design/src/store.ts"],"sourcesContent":["import { Schema } from 'effect'\n\nconst noteFields = {\n project: Schema.String,\n route: Schema.String,\n text: Schema.String,\n}\n\nexport const RectSchema = Schema.Struct({\n x: Schema.Number,\n y: Schema.Number,\n width: Schema.Number,\n height: Schema.Number,\n})\nexport type Rect = typeof RectSchema.Type\n\nconst elementFields = {\n ...noteFields,\n label: Schema.String,\n rect: RectSchema,\n}\n\nconst domFields = {\n kind: Schema.tag('dom'),\n ...elementFields,\n selector: Schema.String,\n}\n\nexport const LoomSourceSchema = Schema.Struct({\n chapter: Schema.String,\n section: Schema.String,\n})\nexport type LoomSource = typeof LoomSourceSchema.Type\n\nconst loomFields = {\n kind: Schema.tag('loom'),\n ...elementFields,\n source: LoomSourceSchema,\n}\n\nconst chatFields = {\n kind: Schema.tag('chat'),\n ...noteFields,\n}\n\nexport const DomDraftSchema = Schema.Struct(domFields)\nexport const LoomDraftSchema = Schema.Struct(loomFields)\nexport const ChatDraftSchema = Schema.Struct(chatFields)\nexport const DraftSchema = Schema.Union([DomDraftSchema, LoomDraftSchema, ChatDraftSchema])\nexport type Draft = typeof DraftSchema.Type\n\nconst stamp = {\n seq: Schema.Number,\n at: Schema.String,\n addressed: Schema.Boolean,\n}\n\nexport const DomNoteSchema = Schema.Struct({ ...domFields, ...stamp })\nexport const LoomNoteSchema = Schema.Struct({ ...loomFields, ...stamp })\nexport const ChatNoteSchema = Schema.Struct({ ...chatFields, ...stamp })\nexport const NoteSchema = Schema.Union([DomNoteSchema, LoomNoteSchema, ChatNoteSchema])\nexport type Note = typeof NoteSchema.Type\n\nimport { Match } from 'effect'\n\nexport const stampDraft = (draft: Draft, seq: number, at: string): Note =>\n Match.value(draft).pipe(\n Match.when({ kind: 'dom' }, (annotation) =>\n DomNoteSchema.make({ ...annotation, seq, at, addressed: false }),\n ),\n Match.when({ kind: 'loom' }, (annotation) =>\n LoomNoteSchema.make({ ...annotation, seq, at, addressed: false }),\n ),\n Match.when({ kind: 'chat' }, (message) =>\n ChatNoteSchema.make({ ...message, seq, at, addressed: false }),\n ),\n Match.exhaustive,\n )\n","import { Effect, Layer, Option } from 'effect'\nimport { SqlClient, SqlError } from 'effect/unstable/sql'\nimport { Data } from 'effect'\n\nclass DecodeError extends Data.TaggedError('DecodeError')<{\n readonly data: string\n readonly cause: unknown\n}> {}\n\nconst migrations: ReadonlyArray<{\n readonly id: number\n readonly name: string\n readonly run: (sql: SqlClient.SqlClient) => Effect.Effect<void, SqlError.SqlError>\n}> = [\n {\n id: 1,\n name: 'initial',\n run: (sql) =>\n Effect.gen(function* () {\n yield* sql`CREATE TABLE IF NOT EXISTS notes (\n project TEXT NOT NULL,\n seq INTEGER NOT NULL,\n data TEXT NOT NULL,\n PRIMARY KEY (project, seq)\n )`\n yield* sql`CREATE TABLE IF NOT EXISTS projects (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL\n )`\n }),\n },\n {\n id: 2,\n name: 'project directory and address',\n run: (sql) =>\n Effect.gen(function* () {\n yield* sql`ALTER TABLE projects ADD COLUMN directory TEXT`\n yield* sql`ALTER TABLE projects ADD COLUMN address TEXT`\n }),\n },\n {\n id: 3,\n name: 'project identity',\n run: (sql) => sql`ALTER TABLE projects ADD COLUMN identity TEXT`.pipe(Effect.asVoid),\n },\n]\n\nconst migrate = (sql: SqlClient.SqlClient): Effect.Effect<void, SqlError.SqlError> =>\n Effect.gen(function* () {\n yield* sql`CREATE TABLE IF NOT EXISTS _migrations (\n id INTEGER PRIMARY KEY,\n name TEXT NOT NULL,\n applied_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP\n )`\n const applied = yield* sql<{ readonly id: number }>`SELECT id FROM _migrations`\n const done = new Set(applied.map((row) => row.id))\n const pending = migrations.filter((migration) => !done.has(migration.id))\n yield* Effect.forEach(\n pending,\n (migration) =>\n sql.withTransaction(\n migration\n .run(sql)\n .pipe(\n Effect.andThen(\n sql`INSERT INTO _migrations (id, name) VALUES (${migration.id}, ${migration.name})`,\n ),\n ),\n ),\n { discard: true },\n )\n })\n\nimport { Array, Schema } from 'effect'\nimport { NoteSchema, type Note } from './note'\n\nconst decodeNote = (data: string): Effect.Effect<Note, DecodeError> =>\n Effect.try({\n try: () => JSON.parse(data) as unknown,\n catch: (cause) => new DecodeError({ data, cause }),\n }).pipe(\n Effect.flatMap((json) =>\n Schema.decodeUnknownEffect(NoteSchema)(json).pipe(\n Effect.mapError((cause) => new DecodeError({ data, cause })),\n ),\n ),\n )\n\nconst readNotes = (\n sql: SqlClient.SqlClient,\n project: string,\n): Effect.Effect<ReadonlyArray<Note>, SqlError.SqlError> =>\n sql<{ readonly data: string }>`\n SELECT data FROM notes WHERE project = ${project} ORDER BY seq\n `.pipe(\n Effect.flatMap((rows) =>\n Effect.forEach(rows, (row) =>\n decodeNote(row.data).pipe(\n Effect.tapError((error) =>\n Effect.logWarning('skipping a note that no longer decodes', error),\n ),\n Effect.map(Option.some),\n Effect.catchTag('DecodeError', () => Effect.succeed(Option.none<Note>())),\n ),\n ),\n ),\n Effect.map(Array.getSomes),\n )\n\nimport { Clock } from 'effect'\nimport { stampDraft, type Draft } from './note'\n\nconst recordDraft = (\n sql: SqlClient.SqlClient,\n draft: Draft,\n): Effect.Effect<Note, SqlError.SqlError> =>\n sql.withTransaction(\n Effect.gen(function* () {\n const millis = yield* Clock.currentTimeMillis\n const rows = yield* sql<{ readonly max: number | null }>`\n SELECT MAX(seq) AS max FROM notes WHERE project = ${draft.project}\n `\n const highest = Option.match(Option.fromNullishOr(rows[0]?.max), {\n onSome: (seq) => seq,\n onNone: () => 0,\n })\n const note = stampDraft(draft, highest + 1, new Date(millis).toISOString())\n yield* sql`\n INSERT INTO notes (project, seq, data)\n VALUES (${note.project}, ${note.seq}, ${JSON.stringify(note)})\n `\n return note\n }),\n )\n\nconst modifyNote = (\n sql: SqlClient.SqlClient,\n project: string,\n seq: number,\n change: (note: Note) => Note,\n): Effect.Effect<void, SqlError.SqlError | DecodeError> =>\n Effect.gen(function* () {\n const rows = yield* sql<{ readonly data: string }>`\n SELECT data FROM notes WHERE project = ${project} AND seq = ${seq}\n `\n yield* Option.match(Option.fromNullishOr(rows[0]), {\n onNone: () => Effect.void,\n onSome: (row) =>\n decodeNote(row.data).pipe(\n Effect.map(change),\n Effect.flatMap((updated) =>\n sql`\n UPDATE notes SET data = ${JSON.stringify(updated)}\n WHERE project = ${project} AND seq = ${seq}\n `,\n ),\n ),\n })\n })\n\nconst resolveNote = (sql: SqlClient.SqlClient, project: string, seq: number) =>\n modifyNote(sql, project, seq, (note) => ({ ...note, addressed: true }))\n\nconst editNote = (sql: SqlClient.SqlClient, project: string, seq: number, text: string) =>\n modifyNote(sql, project, seq, (note) => ({ ...note, text }))\n\nconst discardNote = (\n sql: SqlClient.SqlClient,\n project: string,\n seq: number,\n): Effect.Effect<void, SqlError.SqlError> =>\n sql`DELETE FROM notes WHERE project = ${project} AND seq = ${seq}`.pipe(Effect.asVoid)\n\ntype Project = {\n readonly id: string\n readonly name: string\n readonly directory: string | null\n readonly address: string | null\n readonly identity: string | null\n}\n\nconst listProjects = (\n sql: SqlClient.SqlClient,\n): Effect.Effect<ReadonlyArray<Project>, SqlError.SqlError> =>\n sql<Project>`SELECT id, name, directory, address, identity FROM projects ORDER BY name`\n\nconst findProject = (\n sql: SqlClient.SqlClient,\n name: string,\n): Effect.Effect<Option.Option<Project>, SqlError.SqlError> =>\n sql<Project>`\n SELECT id, name, directory, address, identity FROM projects WHERE id = ${name}\n `.pipe(Effect.map(Array.head))\n\nconst registerProject = (\n sql: SqlClient.SqlClient,\n name: string,\n identity: string,\n directory: string,\n address: string,\n): Effect.Effect<void, SqlError.SqlError> =>\n sql`\n INSERT INTO projects (id, name, directory, address, identity)\n VALUES (${name}, ${name}, ${directory}, ${address}, ${identity})\n ON CONFLICT (id) DO UPDATE SET\n directory = ${directory}, address = ${address}, identity = ${identity}\n `.pipe(Effect.asVoid)\n\nconst ensureProject = (\n sql: SqlClient.SqlClient,\n id: string,\n): Effect.Effect<void, SqlError.SqlError> =>\n sql`\n INSERT INTO projects (id, name) VALUES (${id}, ${id})\n ON CONFLICT (id) DO NOTHING\n `.pipe(Effect.asVoid)\n\nconst backfillProjects = (sql: SqlClient.SqlClient): Effect.Effect<void, SqlError.SqlError> =>\n sql`\n INSERT INTO projects (id, name)\n SELECT DISTINCT project, project FROM notes\n WHERE true\n ON CONFLICT (id) DO NOTHING\n `.pipe(Effect.asVoid)\n\nconst sqliteClientLayer = (\n filename: string,\n): Layer.Layer<SqlClient.SqlClient> =>\n Layer.unwrap(\n Effect.promise((): Promise<Layer.Layer<SqlClient.SqlClient>> =>\n typeof (globalThis as { Bun?: unknown }).Bun !== 'undefined'\n ? import('@effect/sql-sqlite-bun').then((m) =>\n m.SqliteClient.layer({ filename, create: true }),\n )\n : import('@effect/sql-sqlite-node').then((m) =>\n m.SqliteClient.layer({ filename }),\n ),\n ),\n )\n\nexport const sqliteStore = (filename: string): Layer.Layer<NoteStore> =>\n Layer.effect(NoteStore, NoteStore.make).pipe(\n Layer.provide(sqliteClientLayer(filename)),\n )\n\nimport { mkdirSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { dirname, join } from 'node:path'\n\nexport const notesFile = (): string =>\n process.env.LOOM_NOTES_DB ?? join(homedir(), '.loom', 'notes.sqlite')\n\nexport const localStore = (): Layer.Layer<NoteStore> =>\n Layer.unwrap(\n Effect.sync(() => {\n const file = notesFile()\n mkdirSync(dirname(file), { recursive: true })\n return sqliteStore(file)\n }),\n )\n\nimport { Context, PubSub } from 'effect'\n\nexport class NoteStore extends Context.Service<NoteStore>()('NoteStore', {\n make: Effect.gen(function* () {\n const sql = yield* SqlClient.SqlClient\n yield* migrate(sql)\n yield* backfillProjects(sql)\n const changes = yield* PubSub.unbounded<Note>()\n return {\n list: (project: string) => readNotes(sql, project),\n record: (draft: Draft) =>\n ensureProject(sql, draft.project).pipe(\n Effect.andThen(recordDraft(sql, draft)),\n Effect.tap((note) => PubSub.publish(changes, note)),\n ),\n resolve: (project: string, seq: number) => resolveNote(sql, project, seq),\n edit: (project: string, seq: number, text: string) => editNote(sql, project, seq, text),\n discard: (project: string, seq: number) => discardNote(sql, project, seq),\n projects: listProjects(sql),\n project: (name: string) => findProject(sql, name),\n register: (\n name: string,\n identity: string,\n directory: string,\n address: string,\n ) => registerProject(sql, name, identity, directory, address),\n changes,\n } as const\n }).pipe(Effect.orDie),\n}) {\n static readonly layer = localStore()\n}\n"],"mappings":"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"}
package/dist/ui.html ADDED
@@ -0,0 +1,103 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>Loom Design</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=JetBrains+Mono:wght@400;500&family=Newsreader:opsz,wght@6..72,300;6..72,400&display=swap" rel="stylesheet" />
10
+ <style>
11
+ :root {
12
+ --bg: #0f1014;
13
+ --bg-2: #15171d;
14
+ --bg-3: #1b1e26;
15
+ --fg: #e6e6ea;
16
+ --fg-2: #b6b8c2;
17
+ --fg-3: #7a7e8c;
18
+ --rule: #ffffff1f;
19
+ --mint: #8fe0b6;
20
+ --violet: #b59bf1;
21
+ --display: 'Newsreader', Georgia, serif;
22
+ --sans: 'Inter', system-ui, -apple-system, sans-serif;
23
+ --mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
24
+ }
25
+ * { box-sizing: border-box; }
26
+ body {
27
+ margin: 0;
28
+ min-height: 100vh;
29
+ background: var(--bg);
30
+ color: var(--fg);
31
+ font-family: var(--sans);
32
+ display: grid;
33
+ place-items: center;
34
+ padding: 32px;
35
+ }
36
+ .card {
37
+ width: 100%;
38
+ max-width: 640px;
39
+ background: var(--bg-2);
40
+ border: 1px solid var(--rule);
41
+ border-radius: 12px;
42
+ padding: 36px;
43
+ }
44
+ .title {
45
+ font-family: var(--display);
46
+ font-weight: 300;
47
+ font-size: 34px;
48
+ letter-spacing: -0.02em;
49
+ margin-bottom: 12px;
50
+ }
51
+ .title .mint { color: var(--mint); }
52
+ .lede { color: var(--fg-2); line-height: 1.6; }
53
+ .label {
54
+ font-family: var(--mono);
55
+ font-size: 11px;
56
+ letter-spacing: 0.04em;
57
+ color: var(--fg-3);
58
+ margin: 28px 0 8px;
59
+ }
60
+ .input {
61
+ width: 100%;
62
+ background: var(--bg-3);
63
+ border: 1px solid var(--rule);
64
+ border-radius: 6px;
65
+ color: var(--fg);
66
+ font-family: var(--mono);
67
+ font-size: 14px;
68
+ padding: 11px 12px;
69
+ }
70
+ .input:focus { outline: none; border-color: var(--mint); }
71
+ .snippet {
72
+ margin-top: 20px;
73
+ background: var(--bg-3);
74
+ border: 1px solid var(--rule);
75
+ border-radius: 8px;
76
+ padding: 16px;
77
+ font-family: var(--mono);
78
+ font-size: 13px;
79
+ line-height: 1.5;
80
+ color: var(--fg-2);
81
+ white-space: pre-wrap;
82
+ word-break: break-all;
83
+ }
84
+ .copy {
85
+ margin-top: 16px;
86
+ background: var(--mint);
87
+ color: var(--bg);
88
+ border: none;
89
+ border-radius: 6px;
90
+ font-family: var(--mono);
91
+ font-size: 13px;
92
+ font-weight: 500;
93
+ padding: 11px 18px;
94
+ cursor: pointer;
95
+ }
96
+ .copy:hover { background: var(--violet); }
97
+ </style>
98
+ </head>
99
+ <body>
100
+ <div id="app"></div>
101
+ <script type="module" src="/ui.js"></script>
102
+ </body>
103
+ </html>