@ape-egg/codecane 0.0.0-stage → 1.0.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/package.json CHANGED
@@ -1,6 +1,33 @@
1
1
  {
2
2
  "name": "@ape-egg/codecane",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Build a project up step by step: add-only step folders stacked into runnable steps, with a timeline viewer",
5
+ "type": "module",
6
+ "license": "ISC",
7
+ "author": "kkortes",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kkortes/codecane.git",
11
+ "directory": "package"
12
+ },
13
+ "bin": {
14
+ "codecane": "core/cli.js"
15
+ },
16
+ "exports": {
17
+ ".": "./core/index.js",
18
+ "./workflow.md": "./workflow.md"
19
+ },
20
+ "files": [
21
+ "core",
22
+ "ui",
23
+ "workflow.md",
24
+ "README.md"
25
+ ],
26
+ "dependencies": {
27
+ "@ape-egg/codie": "^0.1.18",
28
+ "@ape-egg/vibe": "^4.3.17"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ }
33
+ }
@@ -0,0 +1,534 @@
1
+ :root {
2
+ --line: #2e2e33;
3
+ --dim: #8b8b94;
4
+ --accent: oklch(0.72 0.14 250);
5
+ --addition: oklch(0.76 0.13 145);
6
+ --removal: oklch(0.72 0.15 27);
7
+ --violet: oklch(0.72 0.12 300);
8
+ --yellow: oklch(0.86 0.13 90);
9
+ --text: #e8e8ec;
10
+ --slide: 0.45s cubic-bezier(0.16, 1, 0.3, 1);
11
+ color-scheme: dark;
12
+ font: 0.875rem/1.6 system-ui, sans-serif;
13
+ color: var(--text);
14
+ background: #18181b;
15
+ }
16
+
17
+ body {
18
+ display: grid;
19
+ grid-template-rows: auto minmax(0, 1fr);
20
+ height: 100dvh;
21
+ margin: 0;
22
+ overflow: hidden;
23
+ }
24
+
25
+ header,
26
+ story-panel,
27
+ changes-list {
28
+ position: relative;
29
+ z-index: 1;
30
+ background: #18181b;
31
+ transition: translate var(--slide);
32
+ }
33
+
34
+ header[away] {
35
+ translate: 0 -100%;
36
+ }
37
+
38
+ [away] story-panel {
39
+ translate: -100%;
40
+ }
41
+
42
+ [away] changes-list {
43
+ translate: 100%;
44
+ }
45
+
46
+ [away] code-panel {
47
+ translate: 0 100%;
48
+ }
49
+
50
+ [away] failure card {
51
+ translate: -50% calc(-50% - 100dvh);
52
+ }
53
+
54
+ [fullscreen] stage {
55
+ position: fixed;
56
+ inset: 0;
57
+ }
58
+
59
+ a {
60
+ color: inherit;
61
+ }
62
+
63
+ h2 {
64
+ margin: 0;
65
+ font-size: 0.75rem;
66
+ letter-spacing: 0.06em;
67
+ text-transform: uppercase;
68
+ color: var(--dim);
69
+ }
70
+
71
+ header {
72
+ --bar: 3.8rem;
73
+ display: flex;
74
+ align-items: center;
75
+ min-height: var(--bar);
76
+ padding-left: 1.25rem;
77
+ border-bottom: 1px solid var(--line);
78
+ }
79
+
80
+ version {
81
+ margin-left: 4px;
82
+ font-size: 0.75rem;
83
+ font-weight: 400;
84
+ letter-spacing: normal;
85
+ color: var(--dim);
86
+ }
87
+
88
+ logotext-codecane {
89
+ font-size: 1.125rem;
90
+ font-weight: 700;
91
+ letter-spacing: -0.03em;
92
+ }
93
+
94
+ logotext-codecane span {
95
+ color: var(--yellow);
96
+ }
97
+
98
+ header > button {
99
+ align-self: start;
100
+ width: var(--bar);
101
+ height: var(--bar);
102
+ padding: 0;
103
+ border-radius: 0;
104
+ font-size: 1.375rem;
105
+ }
106
+
107
+ header ol {
108
+ display: grid;
109
+ flex: 1;
110
+ grid-auto-columns: max-content;
111
+ gap: 0 0.25rem;
112
+ transition: grid-template-rows 0.3s ease;
113
+ min-width: 0;
114
+ margin: 0 1.5rem;
115
+ padding: 0.75rem 0;
116
+ overflow-x: auto;
117
+ list-style: none;
118
+ }
119
+
120
+ header li {
121
+ display: flex;
122
+ align-self: center;
123
+ align-items: center;
124
+ gap: 0.25rem;
125
+ max-height: 100%;
126
+ overflow: hidden;
127
+ transition: visibility 0.3s;
128
+ }
129
+
130
+ header li:not([shown]) {
131
+ visibility: hidden;
132
+ }
133
+
134
+ arrow {
135
+ color: var(--dim);
136
+ }
137
+
138
+ header a {
139
+ display: inline-flex;
140
+ align-items: center;
141
+ gap: 0.5rem;
142
+ padding: 0.25rem 0.625rem;
143
+ border-radius: 999px;
144
+ white-space: nowrap;
145
+ text-decoration: none;
146
+ color: var(--dim);
147
+ }
148
+
149
+ header li a {
150
+ --tint: 8%;
151
+ flex: 1;
152
+ }
153
+
154
+ header :is(li a, a[aria-current="page"]) {
155
+ background: color-mix(in oklch, currentColor var(--tint), transparent);
156
+ }
157
+
158
+ header li a:is(:hover, [lit], [aria-current="step"]) {
159
+ --tint: 16%;
160
+ }
161
+
162
+ header li a:not([aria-current="step"]):hover {
163
+ filter: brightness(1.4);
164
+ }
165
+
166
+ logotext-codecane a {
167
+ gap: 0;
168
+ padding-right: 0;
169
+ color: inherit;
170
+ transition: opacity 0.2s;
171
+ }
172
+
173
+ logotext-codecane a:hover {
174
+ opacity: 0.7;
175
+ }
176
+
177
+ header a:is([aria-current="step"], [aria-current="page"], [lit]) {
178
+ --tint: 16%;
179
+ color: inherit;
180
+ }
181
+
182
+ button[fork] {
183
+ display: flex;
184
+ align-items: center;
185
+ gap: 0.25rem;
186
+ padding: 0 0.25rem;
187
+ border: 0;
188
+ font: inherit;
189
+ color: var(--violet);
190
+ background: none;
191
+ cursor: pointer;
192
+ }
193
+
194
+ split {
195
+ rotate: 90deg;
196
+ }
197
+
198
+ join {
199
+ rotate: -90deg;
200
+ }
201
+
202
+ [kind="seam"] {
203
+ --kind: var(--yellow);
204
+ }
205
+
206
+ header a[kind="seam"] {
207
+ --tint: 0%;
208
+ justify-content: center;
209
+ height: 2rem;
210
+ aspect-ratio: 1;
211
+ padding: 0;
212
+ color: var(--kind);
213
+ }
214
+
215
+ convergence {
216
+ display: grid;
217
+ gap: 0.5rem;
218
+ justify-items: start;
219
+ margin-block: 1.25rem;
220
+ padding: 0.75rem;
221
+ border-left: 2px solid var(--kind, var(--line));
222
+ background: #ffffff08;
223
+ }
224
+
225
+ convergence :is(p, ul) {
226
+ margin: 0;
227
+ }
228
+
229
+ convergence h2 {
230
+ color: var(--kind, var(--dim));
231
+ }
232
+
233
+
234
+ workspace {
235
+ --side: minmax(0, 32rem);
236
+ display: grid;
237
+ grid-template: minmax(0, 1fr) / var(--side) minmax(20rem, 1fr) var(--side);
238
+ }
239
+
240
+ story-panel,
241
+ changes-list {
242
+ overflow: auto;
243
+ }
244
+
245
+ story-panel {
246
+ display: flex;
247
+ flex-direction: column;
248
+ border-right: 1px solid var(--line);
249
+ }
250
+
251
+ article,
252
+ file-list {
253
+ padding: 1.25rem;
254
+ }
255
+
256
+ article small {
257
+ color: var(--dim);
258
+ }
259
+
260
+ story h1 {
261
+ margin: 0.25rem 0 0.75rem;
262
+ font-size: 1.375rem;
263
+ line-height: 1.3;
264
+ }
265
+
266
+ story :is(h2, p, ul) {
267
+ margin: 0 0 0.75rem;
268
+ }
269
+
270
+ story code {
271
+ font-family: ui-monospace, monospace;
272
+ padding: 0.0625rem 0.25rem;
273
+ border-radius: 0.25rem;
274
+ background: #2a2a30;
275
+ }
276
+
277
+ story hr {
278
+ border: 0;
279
+ border-top: 1px solid var(--line);
280
+ }
281
+
282
+ story a {
283
+ color: var(--accent);
284
+ }
285
+
286
+ alternatives {
287
+ display: grid;
288
+ gap: 0.25rem;
289
+ margin-top: 1.25rem;
290
+ }
291
+
292
+ alternatives h2 {
293
+ margin-bottom: 0.25rem;
294
+ }
295
+
296
+ alternatives a {
297
+ padding: 0.375rem 0.75rem;
298
+ border-left: 2px solid var(--violet);
299
+ background: #ffffff08;
300
+ color: var(--violet);
301
+ }
302
+
303
+ alternatives a:hover {
304
+ background: #ffffff12;
305
+ }
306
+
307
+ step-links {
308
+ display: flex;
309
+ justify-content: space-between;
310
+ gap: 1rem;
311
+ margin-top: 1.25rem;
312
+ color: var(--dim);
313
+ }
314
+
315
+ step-links a:hover {
316
+ color: var(--text);
317
+ }
318
+
319
+ step-links [next] {
320
+ margin-left: auto;
321
+ }
322
+
323
+ step-links a[kind="seam"] {
324
+ color: var(--kind);
325
+ }
326
+
327
+ file-list {
328
+ flex: 1;
329
+ border-top: 1px solid var(--line);
330
+ }
331
+
332
+ file {
333
+ display: flex;
334
+ align-items: center;
335
+ gap: 0.5rem;
336
+ padding: 0.25rem 0.5rem;
337
+ border-radius: 0.25rem;
338
+ font-family: ui-monospace, monospace;
339
+ cursor: pointer;
340
+ }
341
+
342
+ folder,
343
+ dependency {
344
+ display: block;
345
+ padding: 0.25rem 0.5rem;
346
+ font-family: ui-monospace, monospace;
347
+ color: var(--dim);
348
+ }
349
+
350
+ folder ~ :is(file, dependency) {
351
+ margin-left: 1rem;
352
+ }
353
+
354
+ file span {
355
+ flex: 1;
356
+ }
357
+
358
+ file:hover,
359
+ file[aria-current="location"] {
360
+ background: #2a2a30;
361
+ }
362
+
363
+
364
+ stage {
365
+ position: relative;
366
+ display: grid;
367
+ isolation: isolate;
368
+ overflow: hidden;
369
+ background: #fff;
370
+ }
371
+
372
+ stage > :is(iframe, empty-stage) {
373
+ width: 100%;
374
+ height: 100%;
375
+ border: 0;
376
+ }
377
+
378
+ code-panel {
379
+ position: absolute;
380
+ z-index: 1;
381
+ inset: auto 0 0;
382
+ display: flex;
383
+ flex-direction: column;
384
+ height: var(--panel);
385
+ background: rgb(24 24 27 / 0.8);
386
+ transition: height var(--slide), translate var(--slide);
387
+ animation: rise var(--slide);
388
+ }
389
+
390
+ code-panel[dragging] {
391
+ transition: none;
392
+ }
393
+
394
+ code-panel[closing] {
395
+ animation: sink var(--slide) forwards;
396
+ }
397
+
398
+ @keyframes rise {
399
+ from {
400
+ bottom: calc(var(--panel) * -1);
401
+ }
402
+ }
403
+
404
+ @keyframes sink {
405
+ to {
406
+ bottom: calc(var(--panel) * -1);
407
+ }
408
+ }
409
+
410
+ grip {
411
+ height: 0.375rem;
412
+ cursor: ns-resize;
413
+ touch-action: none;
414
+ }
415
+
416
+ editor-bar {
417
+ display: flex;
418
+ align-items: center;
419
+ gap: 0.75rem;
420
+ padding: 0 0.75rem 0.5rem;
421
+ background: none;
422
+ }
423
+
424
+ editor-bar code {
425
+ margin-right: auto;
426
+ }
427
+
428
+ problem {
429
+ padding: 0.75rem 1rem;
430
+ white-space: pre-wrap;
431
+ font: 0.8125rem/1.5 ui-monospace, monospace;
432
+ color: #fff;
433
+ background: oklch(0.45 0.13 27);
434
+ }
435
+
436
+ code-panel codie[dark] {
437
+ flex: 1;
438
+ contain: size;
439
+ border-radius: 0;
440
+ --codie-bg: transparent;
441
+ }
442
+
443
+ failure {
444
+ position: absolute;
445
+ inset: 0;
446
+ background: oklch(from var(--background) l c h / 0.88);
447
+ }
448
+
449
+ failure card {
450
+ position: absolute;
451
+ top: calc((100% - var(--drawer)) * 0.45);
452
+ left: 50%;
453
+ width: min(32rem, 100% - 2rem);
454
+ translate: -50% -50%;
455
+ transition: top var(--slide), translate var(--slide);
456
+ }
457
+
458
+ [dragging] ~ failure card {
459
+ transition: translate var(--slide);
460
+ }
461
+
462
+ failure message {
463
+ white-space: pre-wrap;
464
+ font: 0.8125rem/1.5 var(--font-mono);
465
+ }
466
+
467
+ failure code {
468
+ align-self: start;
469
+ }
470
+
471
+ empty-stage {
472
+ display: grid;
473
+ place-items: center;
474
+ padding: 1rem;
475
+ color: var(--dim);
476
+ background: #18181b;
477
+ }
478
+
479
+ changes-list {
480
+ grid-column: -2;
481
+ border-left: 1px solid var(--line);
482
+ }
483
+
484
+ :is(file-list, changes-list) > h2 {
485
+ font-size: 0.875rem;
486
+ }
487
+
488
+ changes-list > h2 {
489
+ padding: 1.25rem 1.25rem 0.75rem;
490
+ }
491
+
492
+ change-diff {
493
+ display: flex;
494
+ flex-direction: column;
495
+ margin-bottom: 1rem;
496
+ counter-reset: added removed;
497
+ }
498
+
499
+ change-header {
500
+ order: -1;
501
+ position: sticky;
502
+ top: 0;
503
+ z-index: 1;
504
+ display: flex;
505
+ align-items: center;
506
+ gap: 0.5rem;
507
+ padding: 0.5rem 1.25rem;
508
+ background: #18181b;
509
+ }
510
+
511
+ change-header code {
512
+ min-width: 0;
513
+ margin-right: auto;
514
+ overflow: hidden;
515
+ direction: rtl;
516
+ text-overflow: ellipsis;
517
+ }
518
+
519
+ change-diff codie {
520
+ font-size: 0.75rem;
521
+ --codie-bg: transparent;
522
+ }
523
+
524
+ tally {
525
+ font-family: ui-monospace, monospace;
526
+ }
527
+
528
+ added {
529
+ color: var(--addition);
530
+ }
531
+
532
+ removed {
533
+ color: var(--removal);
534
+ }
@@ -0,0 +1,25 @@
1
+ <changes-list stylecheat dark>
2
+ <h2>Code changes</h2>
3
+ <!-- each changes as change (page.id + change.path) -->
4
+ <change-diff data-state="@[change.state]">
5
+ <!-- if change.binary -->
6
+ <empty-stage>Binary file</empty-stage>
7
+ <!-- else -->
8
+ <codie dark data-path="@[change.path]"></codie>
9
+ <!-- /if -->
10
+ <change-header>
11
+ <code>@[change.path]</code>
12
+ <!-- if change.state === 'added' -->
13
+ <badge bg-green text-green>Added</badge>
14
+ <!-- /if -->
15
+ <!-- if change.state === 'modified' -->
16
+ <badge bg-yellow text-yellow>Modified</badge>
17
+ <!-- /if -->
18
+ <!-- if change.state === 'removed' -->
19
+ <badge bg-red text-red>Removed</badge>
20
+ <!-- /if -->
21
+ <tally><added></added> <removed></removed></tally>
22
+ </change-header>
23
+ </change-diff>
24
+ <!-- /each -->
25
+ </changes-list>
@@ -0,0 +1,40 @@
1
+ <stage style="--drawer: @[open && !closing ? panel : 0]%">
2
+ <!-- if open -->
3
+ <code-panel style="--panel: @[panel]%" dragging="@[dragging]" closing="@[closing]" onanimationend="codecane.settle()">
4
+ <grip onpointerdown="codecane.grab(event)" onpointermove="this.hasPointerCapture(event.pointerId) && codecane.drag(event)" onlostpointercapture="codecane.snap()"></grip>
5
+ <editor-bar stylecheat dark>
6
+ <code>@[open]</code>
7
+ <!-- if editable -->
8
+ <!-- if page.files.find(file => file.path === open).forks.length > 1 -->
9
+ <button onclick="codecane.save(true)" onmouseenter="codecane.light(...$.page.files.find(file => file.path === $.open).forks)" onmouseleave="codecane.light()" secondary sm disabled="@[!(open in drafts)]">Save in @[page.files.find(file => file.path === open).forks.length] forks</button>
10
+ <!-- /if -->
11
+ <button onclick="codecane.save()" onmouseenter="codecane.light('@[page.files.find(file => file.path === open).changedBy]')" onmouseleave="codecane.light()" secondary sm disabled="@[!(open in drafts)]">Save in @[steps[page.files.find(file => file.path === open).changedBy].title]</button>
12
+ <!-- /if -->
13
+ <button onclick="codecane.run()" aria-label="Close" secondary sm><icon cross></icon></button>
14
+ </editor-bar>
15
+ <!-- if problem -->
16
+ <problem>@[problem]</problem>
17
+ <!-- /if -->
18
+ <codie dark></codie>
19
+ </code-panel>
20
+ <!-- /if -->
21
+ <!-- if src -->
22
+ <iframe src="@[src]" title="@[steps[page.id].title]"></iframe>
23
+ <!-- else -->
24
+ <empty-stage>This step has no index.html to run.</empty-stage>
25
+ <!-- /if -->
26
+ <!-- if failure -->
27
+ <failure stylecheat dark role="alert">
28
+ <card g-3>
29
+ <crow justify-between g-4>
30
+ <h4>The step threw</h4>
31
+ <badge bg-red text-red>Error</badge>
32
+ </crow>
33
+ <message>@[failure.message]</message>
34
+ <!-- if failure.at -->
35
+ <code>@[failure.at]</code>
36
+ <!-- /if -->
37
+ </card>
38
+ </failure>
39
+ <!-- /if -->
40
+ </stage>
@@ -0,0 +1,85 @@
1
+ <story-panel ontransitionend="event.target === this && codecane.returned()">
2
+ <article>
3
+ <small>Step @[timeline.indexOf(page.id) + 1] of @[timeline.length]@[page.untested ? ' · Untested' : '']</small>
4
+ <!-- if page.convergence && page.convergence.kind === 'seam' -->
5
+ <story><h1>@[page.title]</h1></story>
6
+ <!-- else -->
7
+ <story>@[$.unsafe(page.story)]</story>
8
+ <!-- /if -->
9
+ <!-- if page.convergence -->
10
+ <convergence kind="@[page.convergence.kind]">
11
+ <h2>@[kinds[page.convergence.kind]]</h2>
12
+ <!-- if page.convergence.kind === 'seam' -->
13
+ <p>A seam is a small API that the timelines before it share. Each timeline fills it in where it owns the code, so the steps after it are written once and never ask which timeline they are on.</p>
14
+ <!-- if page.convergence.requiredBy.length -->
15
+ <p>Required by @[page.convergence.requiredBy.map(id => steps[id].title).join(', ')]:</p>
16
+ <ul>
17
+ <!-- each page.convergence.reasons as reason -->
18
+ <li>@[reason]</li>
19
+ <!-- /each -->
20
+ </ul>
21
+ <!-- /if -->
22
+ <!-- else -->
23
+ <!-- if page.convergence.continues.length -->
24
+ <p>Continues @[page.convergence.continues.map(id => steps[id].title).join(', ')] together, each timeline on its own.</p>
25
+ <!-- /if -->
26
+ <!-- if page.convergence.brought.length -->
27
+ <p>Brings @[page.convergence.brought.map(id => steps[id].title).join(', ')] onto this timeline.</p>
28
+ <!-- /if -->
29
+ <!-- if page.convergence.kind === 'merge' -->
30
+ <p>Every step replays cleanly and every check passes, so the timelines just join.</p>
31
+ <!-- /if -->
32
+ <!-- /if -->
33
+ </convergence>
34
+ <!-- /if -->
35
+ <!-- if page.convergence && page.convergence.kind === 'seam' -->
36
+ <story>@[$.unsafe(page.story)]</story>
37
+ <!-- /if -->
38
+ <!-- if alternatives.length -->
39
+ <alternatives>
40
+ <h2>Alternatives</h2>
41
+ <!-- each alternatives as alternative -->
42
+ <a href="/@[alternative.address]">@[alternative.title]</a>
43
+ <!-- /each -->
44
+ </alternatives>
45
+ <!-- /if -->
46
+ <step-links>
47
+ <!-- if previous -->
48
+ <a href="/@[previous.address]">← @[previous.title]</a>
49
+ <!-- /if -->
50
+ <!-- if next -->
51
+ <a href="/@[next.address]" next kind="@[next.kind]">@[next.title] →</a>
52
+ <!-- else -->
53
+ <span>Final build</span>
54
+ <!-- /if -->
55
+ </step-links>
56
+ </article>
57
+ <file-list stylecheat dark>
58
+ <h2>Files</h2>
59
+ <!-- each [...new Set(page.files.map(file => file.folder))] as folder (page.id + folder) -->
60
+ <!-- if folder -->
61
+ <folder>@[folder]</folder>
62
+ <!-- /if -->
63
+ <!-- each page.files.filter(file => file.folder === folder) as file (page.id + file.path) -->
64
+ <file data-path="@[file.path]" aria-current="@[file.path === open ? 'location' : 'false']" onclick="codecane.open('@[file.path]')" onmouseenter="codecane.light('@[file.changedBy]')" onmouseleave="codecane.light()">
65
+ <span>@[file.path.slice(folder.length)]</span>
66
+ <!-- if file.owner === page.id && file.overrides -->
67
+ <badge bg-orange text-orange>Override</badge>
68
+ <!-- /if -->
69
+ <!-- if file.owner === page.id && !file.overrides -->
70
+ <badge bg-green text-green>Added</badge>
71
+ <!-- /if -->
72
+ <!-- if file.patchedBy.includes(page.id) -->
73
+ <badge bg-yellow text-yellow>Modified</badge>
74
+ <!-- /if -->
75
+ </file>
76
+ <!-- /each -->
77
+ <!-- /each -->
78
+ <!-- if page.dependencies.length -->
79
+ <folder>node_modules/</folder>
80
+ <!-- each page.dependencies as dependency (page.id + dependency) -->
81
+ <dependency>@[dependency]</dependency>
82
+ <!-- /each -->
83
+ <!-- /if -->
84
+ </file-list>
85
+ </story-panel>