abidlabs HF Staff commited on
Commit
bc630c6
·
verified ·
1 Parent(s): 5586fae

Update logbook: Text-to-SQL Post-Training

Browse files
README.md CHANGED
@@ -1,10 +1,16 @@
1
  ---
2
- title: Text2sql Logbook
3
- emoji: 📊
4
- colorFrom: gray
5
- colorTo: purple
6
  sdk: static
7
  pinned: false
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
1
  ---
2
+ title: Text-to-SQL Post-Training
3
+ emoji: 🎯
4
+ colorFrom: yellow
5
+ colorTo: red
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - trackio
10
+ - trackio-logbook
11
+ - open-experiment
12
  ---
13
 
14
+ # Text-to-SQL Post-Training
15
+
16
+ An open experiment logbook, published with [Trackio](https://github.com/gradio-app/trackio).
index.html CHANGED
@@ -1,19 +1,54 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
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>Trackio Logbook</title>
7
+ <link rel="stylesheet" href="./logbook.css" />
8
+ </head>
9
+ <body>
10
+ <div id="app">
11
+ <aside id="sidebar">
12
+ <div id="book-head">
13
+ <img id="book-logo" src="./trackio-logo.png" alt="" />
14
+ <div id="book-title">Logbook</div>
15
+ </div>
16
+ <nav id="tree"></nav>
17
+ <div id="sidebar-foot" hidden>
18
+ <button id="connect-btn" type="button">
19
+ <span class="ico">ⓘ</span> Collaborate with your agent
20
+ </button>
21
+ </div>
22
+ </aside>
23
+ <main id="content">
24
+ <div id="page"></div>
25
+ </main>
26
+ </div>
27
+
28
+ <div id="modal" hidden>
29
+ <div class="modal-backdrop"></div>
30
+ <div class="modal-card" role="dialog" aria-modal="true">
31
+ <div class="modal-head">
32
+ <div class="modal-title">
33
+ <img class="modal-logo" src="./trackio-logo.png" alt="" />
34
+ Collaborate with your agent
35
+ </div>
36
+ <div class="modal-actions">
37
+ <button id="copy-agent" class="btn">Copy for agent</button>
38
+ <button id="modal-close" class="btn icon" aria-label="Close">×</button>
39
+ </div>
40
+ </div>
41
+ <div class="modal-body">
42
+ <p class="modal-intro">
43
+ Point your coding agent at this logbook. It reads a compact,
44
+ token-efficient version — and if you've given it write access to this
45
+ Space, it can add findings that sync back automatically.
46
+ </p>
47
+ <ol id="connect-steps"></ol>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ <script src="./logbook.js"></script>
53
+ </body>
54
  </html>
logbook.css ADDED
@@ -0,0 +1,758 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --bg: #ffffff;
3
+ --paper: #fdfcf9;
4
+ --panel: #ffffff;
5
+ --ink: #1f2937;
6
+ --muted: #6b7280;
7
+ --line: #e5e7eb;
8
+ --accent: #f97316;
9
+ --accent-strong: #ea580c;
10
+ --accent-soft: #fff7ed;
11
+ --accent-line: rgba(249, 115, 22, 0.16);
12
+ --grid-line: rgba(31, 41, 55, 0.045);
13
+ --code-bg: #f3f4f6;
14
+ --radius: 12px;
15
+ --serif: ui-serif, "Iowan Old Style", "Palatino Linotype", Georgia, serif;
16
+ --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial,
17
+ sans-serif;
18
+ --mono: "SF Mono", ui-monospace, "JetBrains Mono", Menlo, Consolas, monospace;
19
+ }
20
+
21
+ * {
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ html,
26
+ body {
27
+ margin: 0;
28
+ padding: 0;
29
+ }
30
+
31
+ body {
32
+ background: var(--bg);
33
+ color: var(--ink);
34
+ font-family: var(--sans);
35
+ font-size: 16px;
36
+ line-height: 1.65;
37
+ -webkit-font-smoothing: antialiased;
38
+ }
39
+
40
+ #app {
41
+ display: flex;
42
+ min-height: 100vh;
43
+ }
44
+
45
+ /* ---- sidebar (composition-book cover) ---- */
46
+ #sidebar {
47
+ width: 280px;
48
+ flex: 0 0 280px;
49
+ background: #17181c;
50
+ color: #e7e7ea;
51
+ position: sticky;
52
+ top: 0;
53
+ height: 100vh;
54
+ overflow-y: auto;
55
+ padding: 22px 16px;
56
+ display: flex;
57
+ flex-direction: column;
58
+ }
59
+
60
+ #book-head {
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 10px;
64
+ padding: 8px;
65
+ margin-bottom: 12px;
66
+ border-radius: 10px;
67
+ cursor: pointer;
68
+ transition: background 0.12s;
69
+ }
70
+ #book-head:hover {
71
+ background: rgba(255, 255, 255, 0.05);
72
+ }
73
+ #book-head.active #book-title {
74
+ color: #fdba74;
75
+ }
76
+
77
+ #book-logo {
78
+ width: 40px;
79
+ height: 40px;
80
+ object-fit: contain;
81
+ flex: 0 0 auto;
82
+ }
83
+
84
+ #book-title {
85
+ font-weight: 700;
86
+ font-size: 15px;
87
+ letter-spacing: -0.01em;
88
+ color: #ffffff;
89
+ }
90
+
91
+ #tree {
92
+ flex: 1;
93
+ padding-top: 8px;
94
+ }
95
+
96
+ #tree a {
97
+ display: block;
98
+ padding: 6px 10px;
99
+ border-radius: 8px;
100
+ color: #c3c4cb;
101
+ text-decoration: none;
102
+ font-size: 14px;
103
+ transition: background 0.12s, color 0.12s;
104
+ }
105
+
106
+ #tree a:hover {
107
+ background: rgba(255, 255, 255, 0.06);
108
+ color: #ffffff;
109
+ }
110
+
111
+ #tree a.active {
112
+ background: rgba(249, 115, 22, 0.16);
113
+ color: #fdba74;
114
+ font-weight: 600;
115
+ }
116
+
117
+ #tree .depth-1 {
118
+ padding-left: 22px;
119
+ }
120
+ #tree .depth-2 {
121
+ padding-left: 34px;
122
+ }
123
+ #tree .depth-3 {
124
+ padding-left: 46px;
125
+ }
126
+
127
+
128
+ /* ---- content ---- */
129
+ #content {
130
+ flex: 1;
131
+ display: flex;
132
+ justify-content: center;
133
+ padding: 48px 40px 120px;
134
+ background-color: var(--paper);
135
+ background-image:
136
+ linear-gradient(var(--grid-line) 1px, transparent 1px),
137
+ linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
138
+ background-size: 26px 26px;
139
+ background-position: center top;
140
+ }
141
+
142
+ #page {
143
+ width: 100%;
144
+ max-width: 760px;
145
+ }
146
+
147
+ #page h1 {
148
+ font-family: var(--serif);
149
+ font-size: 34px;
150
+ line-height: 1.15;
151
+ letter-spacing: -0.02em;
152
+ margin: 0 0 8px;
153
+ }
154
+
155
+ #page h2 {
156
+ font-family: var(--serif);
157
+ font-size: 24px;
158
+ margin: 36px 0 10px;
159
+ }
160
+
161
+ #page h3 {
162
+ font-size: 17px;
163
+ font-weight: 700;
164
+ margin: 26px 0 2px;
165
+ letter-spacing: -0.01em;
166
+ }
167
+
168
+ #page h3::before {
169
+ content: "";
170
+ display: inline-block;
171
+ width: 7px;
172
+ height: 7px;
173
+ border-radius: 2px;
174
+ background: var(--accent);
175
+ margin-right: 10px;
176
+ vertical-align: middle;
177
+ transform: translateY(-1px);
178
+ }
179
+
180
+ #page p {
181
+ margin: 10px 0;
182
+ }
183
+
184
+ #page blockquote {
185
+ margin: 14px 0;
186
+ padding: 2px 16px;
187
+ border-left: 3px solid #fdba74;
188
+ color: var(--muted);
189
+ }
190
+
191
+ #page hr {
192
+ border: none;
193
+ border-top: 1px solid var(--line);
194
+ margin: 30px 0 0;
195
+ }
196
+
197
+ #page code {
198
+ font-family: var(--mono);
199
+ font-size: 0.86em;
200
+ background: var(--code-bg);
201
+ padding: 2px 6px;
202
+ border-radius: 6px;
203
+ }
204
+
205
+ #page pre {
206
+ background: var(--code-bg);
207
+ border: 1px solid var(--line);
208
+ border-radius: var(--radius);
209
+ padding: 14px 16px;
210
+ overflow-x: auto;
211
+ }
212
+ #page pre code {
213
+ background: none;
214
+ padding: 0;
215
+ }
216
+
217
+ /* ---- code blocks + collapsible accordion ---- */
218
+ #page pre.hl {
219
+ background: #17181c;
220
+ border: none;
221
+ color: #e7e7ea;
222
+ font-size: 12.5px;
223
+ line-height: 1.55;
224
+ }
225
+ #page pre.hl code {
226
+ color: inherit;
227
+ font-family: var(--mono);
228
+ }
229
+ .code-accordion {
230
+ border: 1px solid var(--line);
231
+ border-radius: var(--radius);
232
+ overflow: hidden;
233
+ margin: 12px 0;
234
+ background: #17181c;
235
+ }
236
+ .code-accordion summary {
237
+ list-style: none;
238
+ cursor: pointer;
239
+ display: flex;
240
+ align-items: center;
241
+ gap: 9px;
242
+ padding: 10px 14px;
243
+ font-family: var(--mono);
244
+ font-size: 13px;
245
+ color: #e7e7ea;
246
+ background: #1e2027;
247
+ user-select: none;
248
+ }
249
+ .code-accordion summary::-webkit-details-marker {
250
+ display: none;
251
+ }
252
+ .code-accordion summary::before {
253
+ content: "▸";
254
+ color: var(--accent);
255
+ transition: transform 0.12s;
256
+ }
257
+ .code-accordion[open] summary::before {
258
+ transform: rotate(90deg);
259
+ }
260
+ .code-accordion .code-ico {
261
+ color: var(--accent);
262
+ font-weight: 700;
263
+ }
264
+ .code-accordion pre.hl {
265
+ margin: 0;
266
+ border-radius: 0;
267
+ }
268
+ .tok-comment {
269
+ color: #7a7d87;
270
+ font-style: italic;
271
+ }
272
+ .tok-string {
273
+ color: #a5d6a7;
274
+ }
275
+ .tok-keyword {
276
+ color: #fdba74;
277
+ }
278
+ .tok-number {
279
+ color: #7fd0e0;
280
+ }
281
+
282
+ #page a {
283
+ color: var(--accent);
284
+ }
285
+
286
+ #page ul {
287
+ padding-left: 20px;
288
+ }
289
+
290
+ .ts {
291
+ font-family: var(--mono);
292
+ font-size: 12px;
293
+ color: var(--muted);
294
+ background: none;
295
+ padding: 0;
296
+ }
297
+
298
+ /* ---- unfurl cards ---- */
299
+ .unfurl {
300
+ display: block;
301
+ border: 1px solid var(--line);
302
+ border-radius: var(--radius);
303
+ background: var(--panel);
304
+ margin: 12px 0;
305
+ overflow: hidden;
306
+ text-decoration: none;
307
+ color: inherit;
308
+ transition: border-color 0.14s, box-shadow 0.14s;
309
+ }
310
+ .unfurl:hover {
311
+ border-color: #cfcbe6;
312
+ box-shadow: 0 4px 18px rgba(30, 20, 80, 0.06);
313
+ }
314
+
315
+ .unfurl-body {
316
+ padding: 13px 16px;
317
+ display: flex;
318
+ gap: 12px;
319
+ align-items: flex-start;
320
+ }
321
+
322
+ .unfurl-ico {
323
+ font-size: 20px;
324
+ line-height: 1.3;
325
+ flex: 0 0 auto;
326
+ }
327
+
328
+ .unfurl-main {
329
+ min-width: 0;
330
+ flex: 1;
331
+ }
332
+
333
+ .unfurl-kind {
334
+ font-family: var(--mono);
335
+ font-size: 10.5px;
336
+ text-transform: uppercase;
337
+ letter-spacing: 0.08em;
338
+ color: var(--accent);
339
+ font-weight: 600;
340
+ }
341
+
342
+ .unfurl-title {
343
+ font-weight: 650;
344
+ font-size: 15px;
345
+ margin: 1px 0 2px;
346
+ white-space: nowrap;
347
+ overflow: hidden;
348
+ text-overflow: ellipsis;
349
+ }
350
+
351
+ .unfurl-desc {
352
+ color: var(--muted);
353
+ font-size: 13.5px;
354
+ line-height: 1.45;
355
+ }
356
+
357
+ .unfurl-meta {
358
+ margin-top: 6px;
359
+ display: flex;
360
+ flex-wrap: wrap;
361
+ gap: 6px;
362
+ }
363
+
364
+ .chip {
365
+ font-size: 11.5px;
366
+ background: var(--code-bg);
367
+ border-radius: 999px;
368
+ padding: 2px 9px;
369
+ color: var(--muted);
370
+ font-family: var(--mono);
371
+ }
372
+
373
+ .unfurl-raw {
374
+ font-family: var(--mono);
375
+ font-size: 11px;
376
+ color: var(--muted);
377
+ border-top: 1px solid var(--line);
378
+ padding: 7px 16px;
379
+ white-space: nowrap;
380
+ overflow: hidden;
381
+ text-overflow: ellipsis;
382
+ }
383
+
384
+ .unfurl.embed {
385
+ padding: 0;
386
+ overflow: hidden;
387
+ }
388
+ .embed-head {
389
+ display: flex;
390
+ align-items: center;
391
+ gap: 10px;
392
+ padding: 10px 14px;
393
+ border-bottom: 1px solid var(--line);
394
+ }
395
+ .embed-head .unfurl-kind {
396
+ flex: 0 0 auto;
397
+ }
398
+ .embed-title {
399
+ flex: 1;
400
+ min-width: 0;
401
+ font-weight: 650;
402
+ font-size: 14px;
403
+ color: var(--ink);
404
+ text-decoration: none;
405
+ white-space: nowrap;
406
+ overflow: hidden;
407
+ text-overflow: ellipsis;
408
+ }
409
+ .embed-title:hover {
410
+ color: var(--accent);
411
+ }
412
+ .embed-open {
413
+ flex: 0 0 auto;
414
+ font-family: var(--mono);
415
+ font-size: 12px;
416
+ color: var(--accent);
417
+ text-decoration: none;
418
+ }
419
+ .embed-frame {
420
+ display: block;
421
+ width: 100%;
422
+ height: 560px;
423
+ border: 0;
424
+ background: var(--code-bg);
425
+ }
426
+
427
+ .unfurl.image {
428
+ padding: 0;
429
+ }
430
+ .unfurl.image img {
431
+ display: block;
432
+ width: 100%;
433
+ height: auto;
434
+ max-height: 460px;
435
+ object-fit: contain;
436
+ background: var(--code-bg);
437
+ }
438
+
439
+ .artifact-chip {
440
+ border: 1px solid var(--line);
441
+ background: var(--panel);
442
+ border-radius: var(--radius);
443
+ padding: 10px 14px;
444
+ margin: 8px 0;
445
+ font-size: 14px;
446
+ }
447
+ .artifact-chip code {
448
+ color: var(--accent);
449
+ }
450
+
451
+ /* ---- task board ---- */
452
+ .board-wrap {
453
+ overflow-x: auto;
454
+ border: 1px solid var(--line);
455
+ border-radius: var(--radius);
456
+ margin: 12px 0 20px;
457
+ background: var(--panel);
458
+ }
459
+ table.board {
460
+ border-collapse: collapse;
461
+ width: 100%;
462
+ font-size: 14px;
463
+ }
464
+ table.board th,
465
+ table.board td {
466
+ text-align: left;
467
+ padding: 9px 14px;
468
+ border-bottom: 1px solid var(--line);
469
+ vertical-align: top;
470
+ }
471
+ table.board thead th {
472
+ background: var(--accent-soft);
473
+ font-size: 12px;
474
+ text-transform: uppercase;
475
+ letter-spacing: 0.05em;
476
+ color: #9a4a12;
477
+ font-weight: 600;
478
+ border-bottom: 1px solid var(--line);
479
+ }
480
+ table.board tbody tr:last-child td {
481
+ border-bottom: none;
482
+ }
483
+ table.board .col-check {
484
+ text-align: center;
485
+ width: 92px;
486
+ white-space: nowrap;
487
+ }
488
+ table.board tr.section-row td {
489
+ background: var(--accent-soft);
490
+ text-align: center;
491
+ font-weight: 700;
492
+ font-size: 13px;
493
+ color: var(--accent-strong);
494
+ padding: 7px 14px;
495
+ letter-spacing: 0.02em;
496
+ }
497
+ .box {
498
+ display: inline-flex;
499
+ align-items: center;
500
+ justify-content: center;
501
+ width: 18px;
502
+ height: 18px;
503
+ border: 1.5px solid #cfcbe0;
504
+ border-radius: 5px;
505
+ font-size: 12px;
506
+ color: #fff;
507
+ line-height: 1;
508
+ }
509
+ .box.on {
510
+ background: var(--accent);
511
+ border-color: var(--accent);
512
+ }
513
+ .who-chip {
514
+ display: inline-block;
515
+ padding: 3px 12px;
516
+ border-radius: 999px;
517
+ font-size: 12.5px;
518
+ font-weight: 600;
519
+ white-space: nowrap;
520
+ }
521
+ .who-chip.muted {
522
+ background: var(--code-bg);
523
+ color: var(--muted);
524
+ font-weight: 500;
525
+ }
526
+
527
+ /* ---- status badges + clickable rows ---- */
528
+ table.board .col-status {
529
+ width: 130px;
530
+ white-space: nowrap;
531
+ }
532
+ .badge {
533
+ display: inline-block;
534
+ padding: 3px 11px;
535
+ border-radius: 999px;
536
+ font-size: 12px;
537
+ font-weight: 600;
538
+ letter-spacing: 0.01em;
539
+ }
540
+ .badge.gray {
541
+ background: var(--code-bg);
542
+ color: var(--muted);
543
+ }
544
+ .badge.amber {
545
+ background: var(--accent-soft);
546
+ color: #b45309;
547
+ }
548
+ .badge.green {
549
+ background: #e6f7ee;
550
+ color: #1a8a55;
551
+ }
552
+ .badge.red {
553
+ background: #fde8ec;
554
+ color: #c62a4b;
555
+ }
556
+ table.board tr.linked-row {
557
+ cursor: pointer;
558
+ }
559
+ table.board tr.linked-row:hover td {
560
+ background: var(--accent-soft);
561
+ }
562
+ table.board tr.linked-row a {
563
+ color: var(--ink);
564
+ font-weight: 600;
565
+ text-decoration: none;
566
+ }
567
+ table.board tr.linked-row:hover a {
568
+ color: var(--accent-strong);
569
+ }
570
+
571
+ /* ---- connect footer + modal ---- */
572
+ #sidebar-foot {
573
+ margin-top: auto;
574
+ padding-top: 14px;
575
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
576
+ }
577
+
578
+ #connect-btn {
579
+ width: 100%;
580
+ display: flex;
581
+ align-items: center;
582
+ gap: 8px;
583
+ background: rgba(255, 255, 255, 0.05);
584
+ color: #c3c4cb;
585
+ border: 1px solid rgba(255, 255, 255, 0.12);
586
+ border-radius: 9px;
587
+ padding: 9px 12px;
588
+ font-size: 13.5px;
589
+ font-family: var(--sans);
590
+ cursor: pointer;
591
+ transition: background 0.12s, color 0.12s, border-color 0.12s;
592
+ }
593
+ #connect-btn:hover {
594
+ background: rgba(249, 115, 22, 0.14);
595
+ border-color: rgba(249, 115, 22, 0.4);
596
+ color: #fdba74;
597
+ }
598
+ #connect-btn .ico {
599
+ font-size: 15px;
600
+ }
601
+
602
+ #modal[hidden] {
603
+ display: none;
604
+ }
605
+ #modal {
606
+ position: fixed;
607
+ inset: 0;
608
+ z-index: 100;
609
+ display: flex;
610
+ align-items: center;
611
+ justify-content: center;
612
+ padding: 24px;
613
+ }
614
+ .modal-backdrop {
615
+ position: absolute;
616
+ inset: 0;
617
+ background: rgba(20, 18, 30, 0.5);
618
+ backdrop-filter: blur(2px);
619
+ }
620
+ .modal-card {
621
+ position: relative;
622
+ background: var(--panel);
623
+ border-radius: 16px;
624
+ width: 100%;
625
+ max-width: 620px;
626
+ max-height: 85vh;
627
+ overflow-y: auto;
628
+ box-shadow: 0 24px 70px rgba(20, 15, 50, 0.28);
629
+ }
630
+ .modal-head {
631
+ display: flex;
632
+ align-items: center;
633
+ justify-content: space-between;
634
+ gap: 12px;
635
+ padding: 18px 22px;
636
+ border-bottom: 1px solid var(--line);
637
+ position: sticky;
638
+ top: 0;
639
+ background: var(--panel);
640
+ }
641
+ .modal-title {
642
+ display: flex;
643
+ align-items: center;
644
+ gap: 10px;
645
+ font-family: var(--serif);
646
+ font-size: 21px;
647
+ letter-spacing: -0.01em;
648
+ }
649
+ .modal-logo {
650
+ width: 26px;
651
+ height: 26px;
652
+ object-fit: contain;
653
+ }
654
+ .modal-actions {
655
+ display: flex;
656
+ align-items: center;
657
+ gap: 8px;
658
+ }
659
+ .btn {
660
+ font-family: var(--sans);
661
+ font-size: 13.5px;
662
+ font-weight: 600;
663
+ border: 1px solid var(--line);
664
+ background: var(--panel);
665
+ color: var(--ink);
666
+ border-radius: 9px;
667
+ padding: 8px 13px;
668
+ cursor: pointer;
669
+ transition: background 0.12s, border-color 0.12s, color 0.12s;
670
+ }
671
+ .btn:hover {
672
+ border-color: var(--accent);
673
+ color: var(--accent-strong);
674
+ }
675
+ .btn.copied {
676
+ border-color: #1a8a55;
677
+ color: #1a8a55;
678
+ }
679
+ .btn.icon {
680
+ font-size: 18px;
681
+ line-height: 1;
682
+ padding: 6px 11px;
683
+ font-weight: 400;
684
+ }
685
+ .modal-body {
686
+ padding: 20px 22px 26px;
687
+ }
688
+ .modal-intro {
689
+ margin: 0 0 20px;
690
+ color: var(--muted);
691
+ line-height: 1.55;
692
+ }
693
+ #connect-steps {
694
+ list-style: none;
695
+ margin: 0;
696
+ padding: 0;
697
+ }
698
+ #connect-steps li {
699
+ margin-bottom: 18px;
700
+ }
701
+ .step-title {
702
+ font-weight: 600;
703
+ font-size: 14.5px;
704
+ margin-bottom: 8px;
705
+ }
706
+ .codeblock {
707
+ display: flex;
708
+ align-items: center;
709
+ gap: 8px;
710
+ background: #17181c;
711
+ border-radius: 10px;
712
+ padding: 11px 12px 11px 15px;
713
+ }
714
+ .codeblock code {
715
+ flex: 1;
716
+ min-width: 0;
717
+ overflow-x: auto;
718
+ white-space: nowrap;
719
+ font-family: var(--mono);
720
+ font-size: 13px;
721
+ color: #f0efff;
722
+ background: none;
723
+ padding: 0;
724
+ }
725
+ .codeblock .copy {
726
+ flex: 0 0 auto;
727
+ background: rgba(255, 255, 255, 0.08);
728
+ color: #c3c4cb;
729
+ border: 1px solid rgba(255, 255, 255, 0.14);
730
+ border-radius: 7px;
731
+ width: 30px;
732
+ height: 30px;
733
+ font-size: 14px;
734
+ cursor: pointer;
735
+ transition: background 0.12s, color 0.12s;
736
+ }
737
+ .codeblock .copy:hover {
738
+ background: rgba(249, 115, 22, 0.2);
739
+ color: #fdba74;
740
+ }
741
+ .codeblock .copy.copied {
742
+ color: #52d08a;
743
+ }
744
+
745
+ @media (max-width: 720px) {
746
+ #app {
747
+ flex-direction: column;
748
+ }
749
+ #sidebar {
750
+ width: 100%;
751
+ flex: none;
752
+ height: auto;
753
+ position: static;
754
+ }
755
+ #content {
756
+ padding: 28px 20px 80px;
757
+ }
758
+ }
logbook.js ADDED
@@ -0,0 +1,727 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ "use strict";
3
+
4
+ let MANIFEST = null;
5
+ const PAGE_CACHE = {};
6
+ const UNFURL_CACHE = {};
7
+
8
+ function esc(s) {
9
+ return String(s)
10
+ .replace(/&/g, "&amp;")
11
+ .replace(/</g, "&lt;")
12
+ .replace(/>/g, "&gt;");
13
+ }
14
+
15
+ function flattenTree(node, depth, acc) {
16
+ acc.push({ node: node, depth: depth });
17
+ (node.children || []).forEach((c) => flattenTree(c, depth + 1, acc));
18
+ return acc;
19
+ }
20
+
21
+ function findNode(node, slug) {
22
+ if (node.slug === slug) return node;
23
+ for (const c of node.children || []) {
24
+ const hit = findNode(c, slug);
25
+ if (hit) return hit;
26
+ }
27
+ return null;
28
+ }
29
+
30
+ /* -------------------- minimal markdown -------------------- */
31
+
32
+ function inline(text) {
33
+ let t = esc(text);
34
+ t = t.replace(/`([^`]+)`/g, (_, c) => `<code>${c}</code>`);
35
+ t = t.replace(/\*\*([^*]+)\*\*/g, (_, c) => `<strong>${c}</strong>`);
36
+ t = t.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, txt, url) => {
37
+ const safe = esc(url);
38
+ const attrs = /^https?:/.test(url) ? ' target="_blank" rel="noopener"' : "";
39
+ return `<a href="${safe}"${attrs}>${txt}</a>`;
40
+ });
41
+ t = t.replace(/(^|[\s(])(https?:\/\/[^\s<)]+)/g, (m, pre, url) => {
42
+ return `${pre}<a href="${url}" target="_blank" rel="noopener">${url}</a>`;
43
+ });
44
+ return t;
45
+ }
46
+
47
+ const URL_ONLY = /^(https?:\/\/[^\s]+)$/;
48
+
49
+ function renderMarkdown(md, container) {
50
+ const lines = md.replace(/<!--[\s\S]*?-->/g, "").split("\n");
51
+ let i = 0;
52
+ let para = [];
53
+
54
+ function flushPara() {
55
+ if (!para.length) return;
56
+ const joined = para.join(" ").trim();
57
+ para = [];
58
+ if (!joined) return;
59
+ if (URL_ONLY.test(joined) || IMG_PATH.test(joined)) {
60
+ container.appendChild(unfurl(joined));
61
+ return;
62
+ }
63
+ const p = document.createElement("p");
64
+ p.innerHTML = inline(joined);
65
+ container.appendChild(p);
66
+ }
67
+
68
+ while (i < lines.length) {
69
+ const line = lines[i];
70
+ const trimmed = line.trim();
71
+
72
+ if (trimmed === "") {
73
+ flushPara();
74
+ i++;
75
+ continue;
76
+ }
77
+ const fence = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
78
+ if (fence) {
79
+ flushPara();
80
+ const marker = fence[1][0];
81
+ const closeRe = new RegExp("^" + marker + "{" + fence[1].length + ",}\\s*$");
82
+ const info = fence[2].trim();
83
+ const buf = [];
84
+ i++;
85
+ while (i < lines.length && !closeRe.test(lines[i].trim())) {
86
+ buf.push(lines[i]);
87
+ i++;
88
+ }
89
+ i++;
90
+ const lang = (info.split(/\s+/)[0] || "").toLowerCase();
91
+ const tm = info.match(/title=(\S+)/);
92
+ container.appendChild(
93
+ renderCode(buf.join("\n"), lang, tm ? tm[1] : null)
94
+ );
95
+ continue;
96
+ }
97
+ if (trimmed === "---") {
98
+ flushPara();
99
+ container.appendChild(document.createElement("hr"));
100
+ i++;
101
+ continue;
102
+ }
103
+ const h = trimmed.match(/^(#{1,4})\s+(.*)$/);
104
+ if (h) {
105
+ flushPara();
106
+ const el = document.createElement("h" + h[1].length);
107
+ el.innerHTML = inline(h[2]);
108
+ container.appendChild(el);
109
+ i++;
110
+ continue;
111
+ }
112
+ if (
113
+ trimmed.startsWith("|") &&
114
+ i + 1 < lines.length &&
115
+ /^\|?[\s:|-]*-{2,}[\s:|-]*\|?$/.test(lines[i + 1].trim())
116
+ ) {
117
+ flushPara();
118
+ const rows = [];
119
+ while (i < lines.length && lines[i].trim().startsWith("|")) {
120
+ rows.push(parseRow(lines[i].trim()));
121
+ i++;
122
+ }
123
+ renderTable(rows, container);
124
+ continue;
125
+ }
126
+ if (trimmed.startsWith("> ")) {
127
+ flushPara();
128
+ const bq = document.createElement("blockquote");
129
+ bq.innerHTML = inline(trimmed.slice(2));
130
+ container.appendChild(bq);
131
+ i++;
132
+ continue;
133
+ }
134
+ if (/^`[^`]+`$/.test(trimmed)) {
135
+ flushPara();
136
+ const el = document.createElement("div");
137
+ el.className = "ts";
138
+ el.textContent = trimmed.replace(/`/g, "");
139
+ container.appendChild(el);
140
+ i++;
141
+ continue;
142
+ }
143
+ if (trimmed.startsWith("- ")) {
144
+ flushPara();
145
+ const items = [];
146
+ while (i < lines.length && lines[i].trim().startsWith("- ")) {
147
+ items.push(lines[i].trim().slice(2).trim());
148
+ i++;
149
+ }
150
+ renderList(items, container);
151
+ continue;
152
+ }
153
+ para.push(trimmed);
154
+ i++;
155
+ }
156
+ flushPara();
157
+ }
158
+
159
+ function parseRow(line) {
160
+ let s = line.trim();
161
+ if (s.startsWith("|")) s = s.slice(1);
162
+ if (s.endsWith("|")) s = s.slice(0, -1);
163
+ return s.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, "|").trim());
164
+ }
165
+
166
+ const TRUTHY = ["x", "✓", "✔", "yes", "done", "true", "[x]"];
167
+ const CHIP_COLORS = [
168
+ ["#e7f0ff", "#2158d0"],
169
+ ["#fde8ec", "#c62a4b"],
170
+ ["#e6f7ee", "#1a8a55"],
171
+ ["#fdf0e0", "#b26a12"],
172
+ ["#efe9ff", "#5b3bd6"],
173
+ ["#e6f6f8", "#127b88"],
174
+ ];
175
+
176
+ function chipColor(name) {
177
+ let h = 0;
178
+ for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
179
+ return CHIP_COLORS[h % CHIP_COLORS.length];
180
+ }
181
+
182
+ const STATUS_MAP = {
183
+ "": ["Planned", "gray"],
184
+ planned: ["Planned", "gray"],
185
+ todo: ["Planned", "gray"],
186
+ "to do": ["Planned", "gray"],
187
+ backlog: ["Planned", "gray"],
188
+ "in progress": ["In progress", "amber"],
189
+ "in-progress": ["In progress", "amber"],
190
+ wip: ["In progress", "amber"],
191
+ running: ["In progress", "amber"],
192
+ active: ["In progress", "amber"],
193
+ done: ["Done", "green"],
194
+ complete: ["Done", "green"],
195
+ completed: ["Done", "green"],
196
+ blocked: ["Blocked", "red"],
197
+ failed: ["Failed", "red"],
198
+ abandoned: ["Abandoned", "gray"],
199
+ };
200
+
201
+ function statusBadge(val) {
202
+ const [label, tone] = STATUS_MAP[val.toLowerCase()] || [val || "—", "gray"];
203
+ return `<span class="badge ${tone}">${esc(label)}</span>`;
204
+ }
205
+
206
+ function renderTable(rows, container) {
207
+ if (rows.length < 2) return;
208
+ const header = rows[0];
209
+ const body = rows.slice(2);
210
+ const roles = header.map((h) => {
211
+ const t = h.toLowerCase();
212
+ if (t.includes("status") || t.includes("state")) return "status";
213
+ if (t.includes("progress") || t.includes("complete") || t.includes("done"))
214
+ return "check";
215
+ if (t === "who" || t.includes("assign") || t.includes("owner")) return "who";
216
+ return "text";
217
+ });
218
+ const table = document.createElement("table");
219
+ table.className = "board";
220
+ const thead = document.createElement("thead");
221
+ const htr = document.createElement("tr");
222
+ header.forEach((h, c) => {
223
+ const th = document.createElement("th");
224
+ th.textContent = h;
225
+ if (roles[c] === "check") th.className = "col-check";
226
+ htr.appendChild(th);
227
+ });
228
+ thead.appendChild(htr);
229
+ table.appendChild(thead);
230
+ const tbody = document.createElement("tbody");
231
+ body.forEach((cells) => {
232
+ const nonEmpty = cells.filter((x) => x !== "").length;
233
+ if (nonEmpty === 1 && cells[0]) {
234
+ const tr = document.createElement("tr");
235
+ tr.className = "section-row";
236
+ const td = document.createElement("td");
237
+ td.colSpan = header.length;
238
+ td.innerHTML = inline(cells[0]);
239
+ tr.appendChild(td);
240
+ tbody.appendChild(tr);
241
+ return;
242
+ }
243
+ const tr = document.createElement("tr");
244
+ header.forEach((_, c) => {
245
+ const td = document.createElement("td");
246
+ const val = (cells[c] || "").trim();
247
+ if (roles[c] === "status") {
248
+ td.className = "col-status";
249
+ td.innerHTML = statusBadge(val);
250
+ } else if (roles[c] === "check") {
251
+ td.className = "col-check";
252
+ const on = TRUTHY.indexOf(val.toLowerCase()) !== -1;
253
+ td.innerHTML = `<span class="box ${on ? "on" : ""}">${on ? "✓" : ""}</span>`;
254
+ } else if (roles[c] === "who") {
255
+ if (!val || /^to assign$/i.test(val)) {
256
+ td.innerHTML = `<span class="who-chip muted">${esc(val || "—")}</span>`;
257
+ } else {
258
+ const [bg, fg] = chipColor(val);
259
+ td.innerHTML = `<span class="who-chip" style="background:${bg};color:${fg}">${esc(val)}</span>`;
260
+ }
261
+ } else {
262
+ td.innerHTML = inline(val);
263
+ }
264
+ tr.appendChild(td);
265
+ });
266
+ const link = tr.querySelector('a[href^="#/"]');
267
+ if (link) {
268
+ tr.classList.add("linked-row");
269
+ tr.addEventListener("click", (e) => {
270
+ if (e.target.tagName !== "A") location.hash = link.getAttribute("href");
271
+ });
272
+ }
273
+ tbody.appendChild(tr);
274
+ });
275
+ table.appendChild(tbody);
276
+ const wrap = document.createElement("div");
277
+ wrap.className = "board-wrap";
278
+ wrap.appendChild(table);
279
+ container.appendChild(wrap);
280
+ }
281
+
282
+ const HL_RULES = {
283
+ python: [
284
+ ["comment", /#[^\n]*/],
285
+ ["string", /'''[\s\S]*?'''|"""[\s\S]*?"""|'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"/],
286
+ [
287
+ "keyword",
288
+ /\b(?:def|class|return|if|elif|else|for|while|import|from|as|with|try|except|finally|raise|in|not|and|or|is|None|True|False|lambda|yield|global|nonlocal|assert|pass|break|continue|async|await|print)\b/,
289
+ ],
290
+ ["number", /\b\d[\d_.eE+-]*\b/],
291
+ ],
292
+ bash: [
293
+ ["comment", /#[^\n]*/],
294
+ ["string", /'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"/],
295
+ ["keyword", /\b(?:if|then|else|fi|for|in|do|done|while|case|esac|function|export|source|echo|cd|return|local)\b/],
296
+ ["number", /(?<=\s)-{1,2}[a-zA-Z][\w-]*/],
297
+ ],
298
+ json: [
299
+ ["string", /"(?:\\.|[^"\\])*"/],
300
+ ["keyword", /\b(?:true|false|null)\b/],
301
+ ["number", /-?\b\d[\d.eE+-]*\b/],
302
+ ],
303
+ yaml: [
304
+ ["comment", /#[^\n]*/],
305
+ ["string", /'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"/],
306
+ ["keyword", /\b(?:true|false|null|yes|no)\b/],
307
+ ["number", /-?\b\d[\d.eE+-]*\b/],
308
+ ],
309
+ };
310
+ HL_RULES.javascript = HL_RULES.python;
311
+ HL_RULES.typescript = HL_RULES.python;
312
+ HL_RULES.sql = [
313
+ ["comment", /--[^\n]*/],
314
+ ["string", /'(?:\\.|[^'\\])*'/],
315
+ [
316
+ "keyword",
317
+ /\b(?:SELECT|FROM|WHERE|JOIN|LEFT|RIGHT|INNER|OUTER|ON|GROUP|BY|ORDER|LIMIT|INSERT|INTO|VALUES|UPDATE|SET|DELETE|CREATE|TABLE|AS|AND|OR|NOT|NULL|COUNT|DISTINCT|IN)\b/i,
318
+ ],
319
+ ["number", /\b\d[\d.]*\b/],
320
+ ];
321
+
322
+ function highlightCode(code, lang) {
323
+ const rules = HL_RULES[lang];
324
+ if (!rules) return esc(code);
325
+ const combined = new RegExp(rules.map((r) => "(" + r[1].source + ")").join("|"), "g");
326
+ let out = "";
327
+ let last = 0;
328
+ let m;
329
+ while ((m = combined.exec(code))) {
330
+ if (m[0] === "") {
331
+ combined.lastIndex++;
332
+ continue;
333
+ }
334
+ out += esc(code.slice(last, m.index));
335
+ let gi = 1;
336
+ while (gi < m.length && m[gi] === undefined) gi++;
337
+ out += `<span class="tok-${rules[gi - 1][0]}">${esc(m[0])}</span>`;
338
+ last = m.index + m[0].length;
339
+ }
340
+ out += esc(code.slice(last));
341
+ return out;
342
+ }
343
+
344
+ function renderCode(code, lang, title) {
345
+ const pre = document.createElement("pre");
346
+ pre.className = "hl";
347
+ const c = document.createElement("code");
348
+ c.innerHTML = highlightCode(code, lang);
349
+ pre.appendChild(c);
350
+ if (!title) return pre;
351
+ const det = document.createElement("details");
352
+ det.className = "code-accordion";
353
+ const sum = document.createElement("summary");
354
+ sum.innerHTML = `<span class="code-ico">&lt;/&gt;</span> ${esc(title)}`;
355
+ det.appendChild(sum);
356
+ det.appendChild(pre);
357
+ return det;
358
+ }
359
+
360
+ const IMG_PATH = /^[^\s]+\.(png|jpe?g|gif|svg|webp)$/i;
361
+
362
+ function renderList(items, container) {
363
+ let ul = null;
364
+ items.forEach((item) => {
365
+ if (URL_ONLY.test(item) || IMG_PATH.test(item)) {
366
+ ul = null;
367
+ container.appendChild(unfurl(item));
368
+ } else if (item.indexOf("📦 Artifact") !== -1) {
369
+ ul = null;
370
+ const div = document.createElement("div");
371
+ div.className = "artifact-chip";
372
+ div.innerHTML = inline(item);
373
+ container.appendChild(div);
374
+ } else {
375
+ if (!ul) {
376
+ ul = document.createElement("ul");
377
+ container.appendChild(ul);
378
+ }
379
+ const li = document.createElement("li");
380
+ li.innerHTML = inline(item);
381
+ ul.appendChild(li);
382
+ }
383
+ });
384
+ }
385
+
386
+ /* -------------------- unfurl providers -------------------- */
387
+
388
+ function card(url, kind, icon, title, desc, chips) {
389
+ const a = document.createElement("a");
390
+ a.className = "unfurl";
391
+ a.href = url;
392
+ a.target = "_blank";
393
+ a.rel = "noopener";
394
+ const chipHtml = (chips || [])
395
+ .filter(Boolean)
396
+ .map((c) => `<span class="chip">${esc(c)}</span>`)
397
+ .join("");
398
+ a.innerHTML =
399
+ `<div class="unfurl-body">` +
400
+ `<div class="unfurl-ico">${icon}</div>` +
401
+ `<div class="unfurl-main">` +
402
+ `<div class="unfurl-kind">${esc(kind)}</div>` +
403
+ `<div class="unfurl-title">${esc(title)}</div>` +
404
+ (desc ? `<div class="unfurl-desc">${esc(desc)}</div>` : "") +
405
+ (chipHtml ? `<div class="unfurl-meta">${chipHtml}</div>` : "") +
406
+ `</div></div>` +
407
+ `<div class="unfurl-raw">${esc(url)}</div>`;
408
+ return a;
409
+ }
410
+
411
+ function fmt(n) {
412
+ if (n == null) return null;
413
+ if (n >= 1e6) return (n / 1e6).toFixed(1) + "M";
414
+ if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
415
+ return String(n);
416
+ }
417
+
418
+ const providers = [
419
+ {
420
+ test: (u) => /\.(png|jpe?g|gif|svg|webp)(\?|$)/i.test(u) || /\/artifact_blob\//.test(u),
421
+ render: (u, el) => {
422
+ el.className = "unfurl image";
423
+ el.href = u;
424
+ const img = document.createElement("img");
425
+ img.loading = "lazy";
426
+ img.src = u;
427
+ img.alt = "artifact image";
428
+ el.appendChild(img);
429
+ },
430
+ },
431
+ {
432
+ test: (u) => /huggingface\.co\/datasets\//.test(u),
433
+ render: async (u, el) => {
434
+ const id = u.split("/datasets/")[1].split(/[?#]/)[0].replace(/\/$/, "");
435
+ base(el, u, "HF Dataset", "📊", id, "Hugging Face dataset");
436
+ const d = await getJSON(`https://huggingface.co/api/datasets/${id}`);
437
+ if (d)
438
+ fill(el, id, d.cardData?.pretty_name || id, [
439
+ `↓ ${fmt(d.downloads)}`,
440
+ `♥ ${fmt(d.likes)}`,
441
+ ...(d.tags || []).filter((t) => !t.includes(":")).slice(0, 3),
442
+ ]);
443
+ },
444
+ },
445
+ {
446
+ test: (u) => /huggingface\.co\/spaces\//.test(u),
447
+ embed: true,
448
+ render: (u, el) => {
449
+ const id = u.split("/spaces/")[1].split(/[?#]/)[0].replace(/\/$/, "");
450
+ const sub = id.toLowerCase().replace(/[^a-z0-9-]/g, "-");
451
+ el.classList.add("embed");
452
+ el.innerHTML =
453
+ `<div class="embed-head">` +
454
+ `<span class="unfurl-kind">🚀 HF Space</span>` +
455
+ `<a class="embed-title" href="${esc(u)}" target="_blank" rel="noopener">${esc(id)}</a>` +
456
+ `<a class="embed-open" href="${esc(u)}" target="_blank" rel="noopener">Open ↗</a>` +
457
+ `</div>` +
458
+ `<iframe class="embed-frame" src="https://${sub}.hf.space" loading="lazy" ` +
459
+ `allow="clipboard-read; clipboard-write; fullscreen"></iframe>`;
460
+ },
461
+ },
462
+ {
463
+ test: (u) => /huggingface\.co\/jobs\//.test(u),
464
+ render: (u, el) => {
465
+ const rest = u.split("/jobs/")[1].split(/[?#]/)[0].replace(/\/$/, "");
466
+ const parts = rest.split("/");
467
+ const jid = parts[1] || "";
468
+ base(
469
+ el,
470
+ u,
471
+ "HF Job",
472
+ "⚙️",
473
+ `${parts[0]} · ${jid.slice(0, 12)}${jid.length > 12 ? "…" : ""}`,
474
+ "Hugging Face Job — open to view status & logs"
475
+ );
476
+ },
477
+ },
478
+ {
479
+ test: (u) => /huggingface\.co\/buckets\//.test(u),
480
+ render: (u, el) => {
481
+ const id = u.split("/buckets/")[1].split(/[?#]/)[0].replace(/\/$/, "");
482
+ base(el, u, "HF Bucket", "🪣", id, "Hugging Face Bucket — stored artifacts & data");
483
+ },
484
+ },
485
+ {
486
+ test: (u) => /arxiv\.org\/(abs|pdf)\//.test(u),
487
+ render: (u, el) => {
488
+ const id = u.split(/\/(abs|pdf)\//)[2].replace(/\.pdf$/, "");
489
+ base(el, u, "arXiv", "📄", `arXiv:${id}`, "Preprint");
490
+ },
491
+ },
492
+ {
493
+ test: (u) => /github\.com\/[^/]+\/[^/]+/.test(u),
494
+ render: async (u, el) => {
495
+ const m = u.match(/github\.com\/([^/]+)\/([^/?#]+)/);
496
+ const id = `${m[1]}/${m[2]}`;
497
+ base(el, u, "GitHub", "🐙", id, "Repository");
498
+ const d = await getJSON(`https://api.github.com/repos/${id}`);
499
+ if (d)
500
+ fill(el, id, d.description, [
501
+ `★ ${fmt(d.stargazers_count)}`,
502
+ d.language,
503
+ ]);
504
+ },
505
+ },
506
+ {
507
+ test: (u) => /huggingface\.co\/[^/]+\/[^/]+/.test(u),
508
+ render: async (u, el) => {
509
+ const id = u.split("huggingface.co/")[1].split(/[?#]/)[0].replace(/\/$/, "");
510
+ base(el, u, "HF Model", "🤗", id, "Model on the Hugging Face Hub");
511
+ const d = await getJSON(`https://huggingface.co/api/models/${id}`);
512
+ if (d)
513
+ fill(el, id, d.pipeline_tag ? `Task: ${d.pipeline_tag}` : null, [
514
+ `↓ ${fmt(d.downloads)}`,
515
+ `♥ ${fmt(d.likes)}`,
516
+ ...(d.tags || []).filter((t) => !t.includes(":")).slice(0, 2),
517
+ ]);
518
+ },
519
+ },
520
+ ];
521
+
522
+ function base(el, url, kind, icon, title, desc) {
523
+ el.className = "unfurl";
524
+ el.href = url;
525
+ el.innerHTML =
526
+ `<div class="unfurl-body"><div class="unfurl-ico">${icon}</div>` +
527
+ `<div class="unfurl-main"><div class="unfurl-kind">${esc(kind)}</div>` +
528
+ `<div class="unfurl-title">${esc(title)}</div>` +
529
+ `<div class="unfurl-desc">${esc(desc)}</div>` +
530
+ `<div class="unfurl-meta"></div></div></div>` +
531
+ `<div class="unfurl-raw">${esc(url)}</div>`;
532
+ }
533
+
534
+ function fill(el, title, desc, chips) {
535
+ if (title) el.querySelector(".unfurl-title").textContent = title;
536
+ const d = el.querySelector(".unfurl-desc");
537
+ if (desc) d.textContent = desc;
538
+ const meta = el.querySelector(".unfurl-meta");
539
+ meta.innerHTML = (chips || [])
540
+ .filter(Boolean)
541
+ .map((c) => `<span class="chip">${esc(c)}</span>`)
542
+ .join("");
543
+ }
544
+
545
+ async function getJSON(url) {
546
+ if (UNFURL_CACHE[url] !== undefined) return UNFURL_CACHE[url];
547
+ try {
548
+ const r = await fetch(url);
549
+ if (!r.ok) throw new Error(r.status);
550
+ const j = await r.json();
551
+ UNFURL_CACHE[url] = j;
552
+ return j;
553
+ } catch (e) {
554
+ UNFURL_CACHE[url] = null;
555
+ return null;
556
+ }
557
+ }
558
+
559
+ function unfurl(url) {
560
+ const provider = providers.find((p) => p.test(url));
561
+ const el = document.createElement(provider && provider.embed ? "div" : "a");
562
+ el.className = "unfurl";
563
+ if (el.tagName === "A") {
564
+ el.href = url;
565
+ el.target = "_blank";
566
+ el.rel = "noopener";
567
+ }
568
+ if (provider) {
569
+ const out = provider.render(url, el);
570
+ if (out && typeof out.then === "function") out.catch(() => {});
571
+ } else {
572
+ let host = url;
573
+ try {
574
+ host = new URL(url).hostname.replace(/^www\./, "");
575
+ } catch (e) {}
576
+ base(el, url, "Link", "🔗", host, url);
577
+ }
578
+ return el;
579
+ }
580
+
581
+ /* -------------------- routing / render -------------------- */
582
+
583
+ function buildTree() {
584
+ const tree = document.getElementById("tree");
585
+ tree.innerHTML = "";
586
+ const nodes = [];
587
+ (MANIFEST.root.children || []).forEach((c) => flattenTree(c, 0, nodes));
588
+ nodes.forEach(({ node, depth }) => {
589
+ const a = document.createElement("a");
590
+ a.href = "#/" + node.slug;
591
+ a.textContent = node.title;
592
+ a.className = "depth-" + depth;
593
+ a.dataset.slug = node.slug;
594
+ tree.appendChild(a);
595
+ });
596
+ }
597
+
598
+ function highlight(slug) {
599
+ document
600
+ .querySelectorAll("#tree a")
601
+ .forEach((a) => a.classList.toggle("active", a.dataset.slug === slug));
602
+ document
603
+ .getElementById("book-head")
604
+ .classList.toggle("active", slug === MANIFEST.root.slug);
605
+ }
606
+
607
+ async function loadPage(slug) {
608
+ const node = findNode(MANIFEST.root, slug) || MANIFEST.root;
609
+ const page = document.getElementById("page");
610
+ page.innerHTML = "";
611
+ if (!PAGE_CACHE[node.file]) {
612
+ try {
613
+ const r = await fetch("./" + node.file);
614
+ PAGE_CACHE[node.file] = await r.text();
615
+ } catch (e) {
616
+ PAGE_CACHE[node.file] = "# " + node.title + "\n\n_Could not load page._";
617
+ }
618
+ }
619
+ renderMarkdown(PAGE_CACHE[node.file], page);
620
+ highlight(node.slug);
621
+ document.getElementById("content").scrollTo(0, 0);
622
+ window.scrollTo(0, 0);
623
+ }
624
+
625
+ function route() {
626
+ const slug = (location.hash || "").replace(/^#\//, "") || MANIFEST.root.slug;
627
+ loadPage(slug);
628
+ }
629
+
630
+ function setupConnect() {
631
+ const space = MANIFEST.space_id;
632
+ if (!space) return;
633
+ const steps = [
634
+ { t: "Install Trackio, if you don't have it yet.", c: "uv tool install trackio" },
635
+ { t: "Add the Trackio skill for your agent, then reload it.", c: "trackio skills add" },
636
+ { t: "Connect to this logbook.", c: `trackio logbook open ${space}` },
637
+ ];
638
+ const ol = document.getElementById("connect-steps");
639
+ steps.forEach((s, i) => {
640
+ const li = document.createElement("li");
641
+ const title = document.createElement("div");
642
+ title.className = "step-title";
643
+ title.textContent = `${i + 1}. ${s.t}`;
644
+ const block = document.createElement("div");
645
+ block.className = "codeblock";
646
+ const code = document.createElement("code");
647
+ code.textContent = s.c;
648
+ const copy = document.createElement("button");
649
+ copy.className = "copy";
650
+ copy.type = "button";
651
+ copy.title = "Copy";
652
+ copy.textContent = "⧉";
653
+ copy.addEventListener("click", () => copyText(s.c, copy, "⧉"));
654
+ block.appendChild(code);
655
+ block.appendChild(copy);
656
+ li.appendChild(title);
657
+ li.appendChild(block);
658
+ ol.appendChild(li);
659
+ });
660
+
661
+ const agentPrompt =
662
+ `Read and help maintain this Trackio experiment logbook ("${MANIFEST.title}").\n\n` +
663
+ "1. If you don't have Trackio, install it: uv tool install trackio\n" +
664
+ "2. Add the Trackio skill for your agent: trackio skills add (then reload)\n" +
665
+ `3. Connect to this logbook: trackio logbook open ${space}\n\n` +
666
+ "You'll get a compact, token-efficient copy you can read. If I've given you " +
667
+ 'write access to the Space, add findings with `trackio logbook note "..." ' +
668
+ '--experiment "..."` and they will sync back automatically.';
669
+
670
+ const foot = document.getElementById("sidebar-foot");
671
+ foot.hidden = false;
672
+ const modal = document.getElementById("modal");
673
+ const open = () => (modal.hidden = false);
674
+ const close = () => (modal.hidden = true);
675
+ document.getElementById("connect-btn").addEventListener("click", open);
676
+ document.getElementById("modal-close").addEventListener("click", close);
677
+ modal.querySelector(".modal-backdrop").addEventListener("click", close);
678
+ document.addEventListener("keydown", (e) => {
679
+ if (e.key === "Escape") close();
680
+ });
681
+ const agentBtn = document.getElementById("copy-agent");
682
+ agentBtn.addEventListener("click", () =>
683
+ copyText(agentPrompt, agentBtn, "Copy for agent")
684
+ );
685
+ }
686
+
687
+ function copyText(text, btn, restore) {
688
+ const done = () => {
689
+ const prev = btn.textContent;
690
+ btn.textContent = restore === "⧉" ? "✓" : "Copied!";
691
+ btn.classList.add("copied");
692
+ setTimeout(() => {
693
+ btn.textContent = restore;
694
+ btn.classList.remove("copied");
695
+ }, 1400);
696
+ void prev;
697
+ };
698
+ if (navigator.clipboard && navigator.clipboard.writeText) {
699
+ navigator.clipboard.writeText(text).then(done, done);
700
+ } else {
701
+ const ta = document.createElement("textarea");
702
+ ta.value = text;
703
+ document.body.appendChild(ta);
704
+ ta.select();
705
+ try {
706
+ document.execCommand("copy");
707
+ } catch (e) {}
708
+ document.body.removeChild(ta);
709
+ done();
710
+ }
711
+ }
712
+
713
+ async function init() {
714
+ MANIFEST = await (await fetch("./logbook.json")).json();
715
+ document.title = MANIFEST.title + " · Trackio Logbook";
716
+ document.getElementById("book-title").textContent = MANIFEST.title;
717
+ document.getElementById("book-head").addEventListener("click", () => {
718
+ location.hash = "#/" + MANIFEST.root.slug;
719
+ });
720
+ buildTree();
721
+ setupConnect();
722
+ window.addEventListener("hashchange", route);
723
+ route();
724
+ }
725
+
726
+ init();
727
+ })();
logbook.json ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": 1,
3
+ "title": "Text-to-SQL Post-Training",
4
+ "emoji": "🎯",
5
+ "space_id": "abidlabs/text2sql-logbook",
6
+ "updated_at": "2026-07-02T06:19:56+00:00",
7
+ "root": {
8
+ "slug": "index",
9
+ "title": "Text-to-SQL Post-Training",
10
+ "file": "pages/index.md",
11
+ "children": [
12
+ {
13
+ "slug": "prompt-format-ablation-chat-vs-completion",
14
+ "title": "Prompt format ablation (chat vs completion)",
15
+ "file": "pages/prompt-format-ablation-chat-vs-completion/page.md",
16
+ "children": []
17
+ },
18
+ {
19
+ "slug": "add-spider-wikisql-to-the-eval-suite",
20
+ "title": "Add Spider + WikiSQL to the eval suite",
21
+ "file": "pages/add-spider-wikisql-to-the-eval-suite/page.md",
22
+ "children": []
23
+ },
24
+ {
25
+ "slug": "curriculum-order-by-join-complexity",
26
+ "title": "Curriculum: order by join complexity",
27
+ "file": "pages/curriculum-order-by-join-complexity/page.md",
28
+ "children": []
29
+ },
30
+ {
31
+ "slug": "long-context-schema-eval-32k",
32
+ "title": "Long-context schema eval @32k",
33
+ "file": "pages/long-context-schema-eval-32k/page.md",
34
+ "children": []
35
+ },
36
+ {
37
+ "slug": "full-fine-tune-vs-lora-comparison",
38
+ "title": "Full fine-tune vs LoRA comparison",
39
+ "file": "pages/full-fine-tune-vs-lora-comparison/page.md",
40
+ "children": []
41
+ },
42
+ {
43
+ "slug": "error-taxonomy-failure-analysis",
44
+ "title": "Error taxonomy & failure analysis",
45
+ "file": "pages/error-taxonomy-failure-analysis/page.md",
46
+ "children": []
47
+ },
48
+ {
49
+ "slug": "cpu-latency-throughput",
50
+ "title": "CPU latency & throughput",
51
+ "file": "pages/cpu-latency-throughput/page.md",
52
+ "children": []
53
+ },
54
+ {
55
+ "slug": "final-model-card-release",
56
+ "title": "Final model card + release",
57
+ "file": "pages/final-model-card-release/page.md",
58
+ "children": []
59
+ },
60
+ {
61
+ "slug": "build-execution-accuracy-eval-harness",
62
+ "title": "Build execution-accuracy eval harness",
63
+ "file": "pages/build-execution-accuracy-eval-harness/page.md",
64
+ "children": []
65
+ },
66
+ {
67
+ "slug": "zero-shot-baselines-across-open-models",
68
+ "title": "Zero-shot baselines across open models",
69
+ "file": "pages/zero-shot-baselines-across-open-models/page.md",
70
+ "children": []
71
+ },
72
+ {
73
+ "slug": "clean-data-dedup-dialect-filtering",
74
+ "title": "Clean data: dedup + dialect filtering",
75
+ "file": "pages/clean-data-dedup-dialect-filtering/page.md",
76
+ "children": []
77
+ },
78
+ {
79
+ "slug": "qlora-sft-baseline",
80
+ "title": "QLoRA SFT baseline",
81
+ "file": "pages/qlora-sft-baseline/page.md",
82
+ "children": []
83
+ },
84
+ {
85
+ "slug": "lr-lora-rank-sweep",
86
+ "title": "LR & LoRA-rank sweep",
87
+ "file": "pages/lr-lora-rank-sweep/page.md",
88
+ "children": []
89
+ },
90
+ {
91
+ "slug": "synthetic-data-augmentation-self-instruct",
92
+ "title": "Synthetic data augmentation (self-instruct)",
93
+ "file": "pages/synthetic-data-augmentation-self-instruct/page.md",
94
+ "children": []
95
+ },
96
+ {
97
+ "slug": "distill-from-a-larger-open-model",
98
+ "title": "Distill from a larger open model",
99
+ "file": "pages/distill-from-a-larger-open-model/page.md",
100
+ "children": []
101
+ }
102
+ ]
103
+ }
104
+ }
logbook.md ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Text-to-SQL Post-Training
2
+
3
+ # Text-to-SQL Post-Training
4
+
5
+ > A multi-week campaign to post-train a small open model into a strong text-to-SQL generator, scored by **execution accuracy** (run gold vs predicted SQL against a real SQLite DB). Click an experiment to open its page.
6
+
7
+ ## Experiments
8
+
9
+ | Status | Experiment | Owner |
10
+ | --- | --- | --- |
11
+ | **Week 1 — Foundations & baselines** | | |
12
+ | done | [Build execution-accuracy eval harness](#/build-execution-accuracy-eval-harness) | Ana |
13
+ | done | [Zero-shot baselines across open models](#/zero-shot-baselines-across-open-models) | Ana |
14
+ | done | [Clean data: dedup + dialect filtering](#/clean-data-dedup-dialect-filtering) | Ana |
15
+ | done | [QLoRA SFT baseline](#/qlora-sft-baseline) | Ravi |
16
+ | in-progress | [LR & LoRA-rank sweep](#/lr-lora-rank-sweep) | Ravi |
17
+ | planned | [Prompt format ablation (chat vs completion)](#/prompt-format-ablation-chat-vs-completion) | to assign |
18
+ | **Week 2 — Scaling & data** | | |
19
+ | in-progress | [Synthetic data augmentation (self-instruct)](#/synthetic-data-augmentation-self-instruct) | Ravi |
20
+ | planned | [Add Spider + WikiSQL to the eval suite](#/add-spider-wikisql-to-the-eval-suite) | Ana |
21
+ | planned | [Curriculum: order by join complexity](#/curriculum-order-by-join-complexity) | to assign |
22
+ | planned | [Distill from a larger open model](#/distill-from-a-larger-open-model) | Ravi |
23
+ | blocked | [Long-context schema eval @32k](#/long-context-schema-eval-32k) | to assign |
24
+ | **Week 3 — Hardening & release** | | |
25
+ | planned | [Full fine-tune vs LoRA comparison](#/full-fine-tune-vs-lora-comparison) | Ravi |
26
+ | planned | [Error taxonomy & failure analysis](#/error-taxonomy-failure-analysis) | Ana |
27
+ | planned | [CPU latency & throughput](#/cpu-latency-throughput) | to assign |
28
+ | planned | [Final model card + release](#/final-model-card-release) | Ana |
29
+
30
+ # Prompt format ablation (chat vs completion)
31
+
32
+ # Add Spider + WikiSQL to the eval suite
33
+
34
+ # Curriculum: order by join complexity
35
+
36
+ # Long-context schema eval @32k
37
+
38
+ # Full fine-tune vs LoRA comparison
39
+
40
+ # Error taxonomy & failure analysis
41
+
42
+ # CPU latency & throughput
43
+
44
+ # Final model card + release
45
+
46
+ # Build execution-accuracy eval harness
47
+
48
+ ---
49
+
50
+ ### Harness: execution accuracy over SQLite
51
+
52
+ `Jul 02, 2026 · 06:19 UTC`
53
+
54
+ Execution accuracy is the right metric: exact string match is near-zero because the model writes semantically-equivalent but syntactically-varied SQL. The harness builds an in-memory SQLite DB from each example's schema, runs gold and predicted queries, and compares result sets (order-aware only when the gold has ORDER BY).
55
+
56
+
57
+ ````python title=eval.py
58
+ import sqlite3
59
+ from datasets import load_dataset
60
+
61
+ def execution_accuracy(preds, golds, schemas):
62
+ """Build an in-memory SQLite DB per example, run gold vs pred, compare result sets."""
63
+ correct = 0
64
+ for pred, gold, schema in zip(preds, golds, schemas):
65
+ con = sqlite3.connect(":memory:")
66
+ con.executescript(schema)
67
+ try:
68
+ got = con.execute(pred).fetchall()
69
+ want = con.execute(gold).fetchall()
70
+ correct += set(map(tuple, got)) == set(map(tuple, want))
71
+ except sqlite3.Error:
72
+ pass
73
+ return correct / len(preds)
74
+
75
+ ````
76
+
77
+ - https://github.com/huggingface/trl
78
+
79
+ # Zero-shot baselines across open models
80
+
81
+ ---
82
+
83
+ ### Baselines: 28.9% best zero-shot
84
+
85
+ `Jul 02, 2026 · 06:19 UTC`
86
+
87
+ Zero-shot execution accuracy on the 800-example held-out set. Instruct variants lead; the 1.5B instruct model is the best base to fine-tune from.
88
+
89
+ | Model | Exec. accuracy | Exact match |
90
+ | --- | --- | --- |
91
+ | google/gemma-3-270m | 12.1% | 0.1% |
92
+ | meta-llama/Llama-3.2-1B-Instruct | 21.7% | 3.2% |
93
+ | Qwen/Qwen2.5-1.5B-Instruct | **28.9%** | 4.4% |
94
+
95
+ Target to beat with SFT: **28.9%**.
96
+
97
+ - https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct
98
+ - https://huggingface.co/meta-llama/Llama-3.2-1B-Instruct
99
+ - https://huggingface.co/datasets/gretelai/synthetic_text_to_sql
100
+
101
+ # Clean data: dedup + dialect filtering
102
+
103
+ ---
104
+
105
+ ### Data: 42k clean SQLite-executable examples
106
+
107
+ `Jul 02, 2026 · 06:19 UTC`
108
+
109
+ Filtered the training set to examples whose gold query executes cleanly in SQLite (~78% do; the rest use non-SQLite dialects), then deduped against the eval prompts. Final training set: 42k examples.
110
+
111
+ # QLoRA SFT baseline
112
+
113
+ ---
114
+
115
+ ### QLoRA baseline: 51.3% exec acc
116
+
117
+ `Jul 02, 2026 · 06:19 UTC`
118
+
119
+ First SFT pass: QLoRA (r=16) on Qwen2.5-1.5B-Instruct, 3 epochs, completion-only loss. Execution accuracy 28.9% → **51.3%**. Live metrics on the Trackio dashboard.
120
+
121
+
122
+ ````python title=train.py
123
+ import trackio
124
+ from datasets import load_dataset
125
+ from trl import SFTConfig, SFTTrainer
126
+ from peft import LoraConfig
127
+
128
+ def main(model="Qwen/Qwen2.5-1.5B-Instruct", r=16, lr=2e-4):
129
+ ds = load_dataset("gretelai/synthetic_text_to_sql", split="train")
130
+ trackio.init(project="text2sql", config={"model": model, "r": r, "lr": lr})
131
+ cfg = SFTConfig(learning_rate=lr, num_train_epochs=3,
132
+ per_device_train_batch_size=16, report_to="trackio")
133
+ peft = LoraConfig(r=r, lora_alpha=2 * r, task_type="CAUSAL_LM")
134
+ SFTTrainer(model, args=cfg, train_dataset=ds, peft_config=peft).train()
135
+
136
+ if __name__ == "__main__":
137
+ main()
138
+
139
+ ````
140
+
141
+ - https://huggingface.co/spaces/abidlabs/gemma-text2sql-trackio
142
+
143
+ # LR & LoRA-rank sweep
144
+
145
+ ---
146
+
147
+ ### Sweep: r=16, lr=5e-4 wins
148
+
149
+ `Jul 02, 2026 · 06:19 UTC`
150
+
151
+ Swept learning rate {1e-4, 2e-4, 5e-4} × rank {8, 16, 32}. r=16 / lr=5e-4 is the clear winner; r=8 underfits and lr>5e-4 destabilizes late in training.
152
+
153
+ - media/lr_rank_sweep.png
154
+ - https://huggingface.co/spaces/abidlabs/gemma-text2sql-trackio
155
+
156
+ # Synthetic data augmentation (self-instruct)
157
+
158
+ ---
159
+
160
+ ### Synth data: +3.1% exec acc (early)
161
+
162
+ `Jul 02, 2026 · 06:19 UTC`
163
+
164
+ Generating extra (question, SQL) pairs by prompting a larger open model on real schemas, keeping only pairs whose SQL executes. Running as an HF Job; outputs land in a bucket. Early signal: +3.1% exec acc when mixed 1:4 with real data.
165
+
166
+
167
+ ````python title=gen_synth.py
168
+ """Self-instruct augmentation: sample real schemas, prompt a teacher model for
169
+ new (question, SQL) pairs, then keep only pairs whose SQL executes."""
170
+ import json, sqlite3, random
171
+ from huggingface_hub import InferenceClient
172
+
173
+ client = InferenceClient()
174
+
175
+ def augment(schemas, n_per_schema=8):
176
+ out = []
177
+ for schema in schemas:
178
+ prompt = f"Given this schema, write {n_per_schema} diverse NL questions "\
179
+ f"and their SQLite queries as JSONL.\n{schema}"
180
+ for line in client.text_generation(prompt, max_new_tokens=1024).splitlines():
181
+ try:
182
+ ex = json.loads(line)
183
+ sqlite3.connect(":memory:").executescript(schema).execute(ex["sql"])
184
+ out.append({**ex, "schema": schema})
185
+ except Exception:
186
+ continue
187
+ return out
188
+
189
+ ````
190
+
191
+ - https://huggingface.co/jobs/abidlabs/6a45b02733c08a2c0dae0348
192
+ - https://huggingface.co/buckets/abidlabs/jobs-artifacts
193
+
194
+ # Distill from a larger open model
195
+
196
+ ---
197
+
198
+ ### Plan & hypothesis
199
+
200
+ `Jul 02, 2026 · 06:19 UTC`
201
+
202
+ Plan: use the best open model as a teacher (rationale + SQL), distill into the 1.5B student. Hypothesis: closes most of the gap to the teacher at a fraction of the cost.
pages/add-spider-wikisql-to-the-eval-suite/page.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Add Spider + WikiSQL to the eval suite
pages/build-execution-accuracy-eval-harness/page.md ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Build execution-accuracy eval harness
2
+
3
+ ---
4
+
5
+ ### Harness: execution accuracy over SQLite
6
+ <!-- entry ts=2026-07-02T06:19:56+00:00 -->
7
+ `Jul 02, 2026 · 06:19 UTC`
8
+
9
+ Execution accuracy is the right metric: exact string match is near-zero because the model writes semantically-equivalent but syntactically-varied SQL. The harness builds an in-memory SQLite DB from each example's schema, runs gold and predicted queries, and compares result sets (order-aware only when the gold has ORDER BY).
10
+
11
+
12
+ ````python title=eval.py
13
+ import sqlite3
14
+ from datasets import load_dataset
15
+
16
+ def execution_accuracy(preds, golds, schemas):
17
+ """Build an in-memory SQLite DB per example, run gold vs pred, compare result sets."""
18
+ correct = 0
19
+ for pred, gold, schema in zip(preds, golds, schemas):
20
+ con = sqlite3.connect(":memory:")
21
+ con.executescript(schema)
22
+ try:
23
+ got = con.execute(pred).fetchall()
24
+ want = con.execute(gold).fetchall()
25
+ correct += set(map(tuple, got)) == set(map(tuple, want))
26
+ except sqlite3.Error:
27
+ pass
28
+ return correct / len(preds)
29
+
30
+ ````
31
+
32
+ - https://github.com/huggingface/trl
pages/clean-data-dedup-dialect-filtering/page.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Clean data: dedup + dialect filtering
2
+
3
+ ---
4
+
5
+ ### Data: 42k clean SQLite-executable examples
6
+ <!-- entry ts=2026-07-02T06:19:56+00:00 -->
7
+ `Jul 02, 2026 · 06:19 UTC`
8
+
9
+ Filtered the training set to examples whose gold query executes cleanly in SQLite (~78% do; the rest use non-SQLite dialects), then deduped against the eval prompts. Final training set: 42k examples.
pages/cpu-latency-throughput/page.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # CPU latency & throughput
pages/curriculum-order-by-join-complexity/page.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Curriculum: order by join complexity
pages/distill-from-a-larger-open-model/page.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Distill from a larger open model
2
+
3
+ ---
4
+
5
+ ### Plan & hypothesis
6
+ <!-- entry ts=2026-07-02T06:19:56+00:00 -->
7
+ `Jul 02, 2026 · 06:19 UTC`
8
+
9
+ Plan: use the best open model as a teacher (rationale + SQL), distill into the 1.5B student. Hypothesis: closes most of the gap to the teacher at a fraction of the cost.
pages/error-taxonomy-failure-analysis/page.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Error taxonomy & failure analysis
pages/final-model-card-release/page.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Final model card + release
pages/full-fine-tune-vs-lora-comparison/page.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Full fine-tune vs LoRA comparison
pages/index.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Text-to-SQL Post-Training
2
+
3
+ > A multi-week campaign to post-train a small open model into a strong text-to-SQL generator, scored by **execution accuracy** (run gold vs predicted SQL against a real SQLite DB). Click an experiment to open its page.
4
+
5
+ ## Experiments
6
+
7
+ | Status | Experiment | Owner |
8
+ | --- | --- | --- |
9
+ | **Week 1 — Foundations & baselines** | | |
10
+ | done | [Build execution-accuracy eval harness](#/build-execution-accuracy-eval-harness) | Ana |
11
+ | done | [Zero-shot baselines across open models](#/zero-shot-baselines-across-open-models) | Ana |
12
+ | done | [Clean data: dedup + dialect filtering](#/clean-data-dedup-dialect-filtering) | Ana |
13
+ | done | [QLoRA SFT baseline](#/qlora-sft-baseline) | Ravi |
14
+ | in-progress | [LR & LoRA-rank sweep](#/lr-lora-rank-sweep) | Ravi |
15
+ | planned | [Prompt format ablation (chat vs completion)](#/prompt-format-ablation-chat-vs-completion) | to assign |
16
+ | **Week 2 — Scaling & data** | | |
17
+ | in-progress | [Synthetic data augmentation (self-instruct)](#/synthetic-data-augmentation-self-instruct) | Ravi |
18
+ | planned | [Add Spider + WikiSQL to the eval suite](#/add-spider-wikisql-to-the-eval-suite) | Ana |
19
+ | planned | [Curriculum: order by join complexity](#/curriculum-order-by-join-complexity) | to assign |
20
+ | planned | [Distill from a larger open model](#/distill-from-a-larger-open-model) | Ravi |
21
+ | blocked | [Long-context schema eval @32k](#/long-context-schema-eval-32k) | to assign |
22
+ | **Week 3 — Hardening & release** | | |
23
+ | planned | [Full fine-tune vs LoRA comparison](#/full-fine-tune-vs-lora-comparison) | Ravi |
24
+ | planned | [Error taxonomy & failure analysis](#/error-taxonomy-failure-analysis) | Ana |
25
+ | planned | [CPU latency & throughput](#/cpu-latency-throughput) | to assign |
26
+ | planned | [Final model card + release](#/final-model-card-release) | Ana |
pages/long-context-schema-eval-32k/page.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Long-context schema eval @32k
pages/lr-lora-rank-sweep/page.md ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LR & LoRA-rank sweep
2
+
3
+ ---
4
+
5
+ ### Sweep: r=16, lr=5e-4 wins
6
+ <!-- entry ts=2026-07-02T06:19:56+00:00 -->
7
+ `Jul 02, 2026 · 06:19 UTC`
8
+
9
+ Swept learning rate {1e-4, 2e-4, 5e-4} × rank {8, 16, 32}. r=16 / lr=5e-4 is the clear winner; r=8 underfits and lr>5e-4 destabilizes late in training.
10
+
11
+ - media/lr_rank_sweep.png
12
+ - https://huggingface.co/spaces/abidlabs/gemma-text2sql-trackio
pages/prompt-format-ablation-chat-vs-completion/page.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Prompt format ablation (chat vs completion)
pages/qlora-sft-baseline/page.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # QLoRA SFT baseline
2
+
3
+ ---
4
+
5
+ ### QLoRA baseline: 51.3% exec acc
6
+ <!-- entry ts=2026-07-02T06:19:56+00:00 -->
7
+ `Jul 02, 2026 · 06:19 UTC`
8
+
9
+ First SFT pass: QLoRA (r=16) on Qwen2.5-1.5B-Instruct, 3 epochs, completion-only loss. Execution accuracy 28.9% → **51.3%**. Live metrics on the Trackio dashboard.
10
+
11
+
12
+ ````python title=train.py
13
+ import trackio
14
+ from datasets import load_dataset
15
+ from trl import SFTConfig, SFTTrainer
16
+ from peft import LoraConfig
17
+
18
+ def main(model="Qwen/Qwen2.5-1.5B-Instruct", r=16, lr=2e-4):
19
+ ds = load_dataset("gretelai/synthetic_text_to_sql", split="train")
20
+ trackio.init(project="text2sql", config={"model": model, "r": r, "lr": lr})
21
+ cfg = SFTConfig(learning_rate=lr, num_train_epochs=3,
22
+ per_device_train_batch_size=16, report_to="trackio")
23
+ peft = LoraConfig(r=r, lora_alpha=2 * r, task_type="CAUSAL_LM")
24
+ SFTTrainer(model, args=cfg, train_dataset=ds, peft_config=peft).train()
25
+
26
+ if __name__ == "__main__":
27
+ main()
28
+
29
+ ````
30
+
31
+ - https://huggingface.co/spaces/abidlabs/gemma-text2sql-trackio
pages/synthetic-data-augmentation-self-instruct/page.md ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Synthetic data augmentation (self-instruct)
2
+
3
+ ---
4
+
5
+ ### Synth data: +3.1% exec acc (early)
6
+ <!-- entry ts=2026-07-02T06:19:56+00:00 -->
7
+ `Jul 02, 2026 · 06:19 UTC`
8
+
9
+ Generating extra (question, SQL) pairs by prompting a larger open model on real schemas, keeping only pairs whose SQL executes. Running as an HF Job; outputs land in a bucket. Early signal: +3.1% exec acc when mixed 1:4 with real data.
10
+
11
+
12
+ ````python title=gen_synth.py
13
+ """Self-instruct augmentation: sample real schemas, prompt a teacher model for
14
+ new (question, SQL) pairs, then keep only pairs whose SQL executes."""
15
+ import json, sqlite3, random
16
+ from huggingface_hub import InferenceClient
17
+
18
+ client = InferenceClient()
19
+
20
+ def augment(schemas, n_per_schema=8):
21
+ out = []
22
+ for schema in schemas:
23
+ prompt = f"Given this schema, write {n_per_schema} diverse NL questions "\
24
+ f"and their SQLite queries as JSONL.\n{schema}"
25
+ for line in client.text_generation(prompt, max_new_tokens=1024).splitlines():
26
+ try:
27
+ ex = json.loads(line)
28
+ sqlite3.connect(":memory:").executescript(schema).execute(ex["sql"])
29
+ out.append({**ex, "schema": schema})
30
+ except Exception:
31
+ continue
32
+ return out
33
+
34
+ ````
35
+
36
+ - https://huggingface.co/jobs/abidlabs/6a45b02733c08a2c0dae0348
37
+ - https://huggingface.co/buckets/abidlabs/jobs-artifacts
pages/zero-shot-baselines-across-open-models/page.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Zero-shot baselines across open models
2
+
3
+ ---
4
+
5
+ ### Baselines: 28.9% best zero-shot
6
+ <!-- entry ts=2026-07-02T06:19:56+00:00 -->
7
+ `Jul 02, 2026 · 06:19 UTC`
8
+
9
+ Zero-shot execution accuracy on the 800-example held-out set. Instruct variants lead; the 1.5B instruct model is the best base to fine-tune from.
10
+
11
+ | Model | Exec. accuracy | Exact match |
12
+ | --- | --- | --- |
13
+ | google/gemma-3-270m | 12.1% | 0.1% |
14
+ | meta-llama/Llama-3.2-1B-Instruct | 21.7% | 3.2% |
15
+ | Qwen/Qwen2.5-1.5B-Instruct | **28.9%** | 4.4% |
16
+
17
+ Target to beat with SFT: **28.9%**.
18
+
19
+ - https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct
20
+ - https://huggingface.co/meta-llama/Llama-3.2-1B-Instruct
21
+ - https://huggingface.co/datasets/gretelai/synthetic_text_to_sql
trackio-logo.png ADDED