Spaces:
Running
Running
Update logbook: Text-to-SQL Post-Training
Browse files- README.md +11 -5
- index.html +52 -17
- logbook.css +758 -0
- logbook.js +727 -0
- logbook.json +104 -0
- logbook.md +202 -0
- pages/add-spider-wikisql-to-the-eval-suite/page.md +1 -0
- pages/build-execution-accuracy-eval-harness/page.md +32 -0
- pages/clean-data-dedup-dialect-filtering/page.md +9 -0
- pages/cpu-latency-throughput/page.md +1 -0
- pages/curriculum-order-by-join-complexity/page.md +1 -0
- pages/distill-from-a-larger-open-model/page.md +9 -0
- pages/error-taxonomy-failure-analysis/page.md +1 -0
- pages/final-model-card-release/page.md +1 -0
- pages/full-fine-tune-vs-lora-comparison/page.md +1 -0
- pages/index.md +26 -0
- pages/long-context-schema-eval-32k/page.md +1 -0
- pages/lr-lora-rank-sweep/page.md +12 -0
- pages/prompt-format-ablation-chat-vs-completion/page.md +1 -0
- pages/qlora-sft-baseline/page.md +31 -0
- pages/synthetic-data-augmentation-self-instruct/page.md +37 -0
- pages/zero-shot-baselines-across-open-models/page.md +21 -0
- trackio-logo.png +0 -0
README.md
CHANGED
|
@@ -1,10 +1,16 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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, "&")
|
| 11 |
+
.replace(/</g, "<")
|
| 12 |
+
.replace(/>/g, ">");
|
| 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"></></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
|