tony-cli 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tony_cli/__init__.py +0 -0
- tony_cli/agent.py +767 -0
- tony_cli/assets/fonts/Favorit-Bold.woff2 +0 -0
- tony_cli/assets/fonts/Favorit-Book.woff2 +0 -0
- tony_cli/assets/fonts/Favorit-Regular.woff2 +0 -0
- tony_cli/assets/fonts/FavoritMono-Medium.woff2 +0 -0
- tony_cli/assets/fonts/FavoritMono-Regular.woff2 +0 -0
- tony_cli/assets/viewer.css +281 -0
- tony_cli/assets/viewer.js +98 -0
- tony_cli/fonts.py +38 -0
- tony_cli/hosted.py +324 -0
- tony_cli/install.py +138 -0
- tony_cli/layout.py +233 -0
- tony_cli/page.py +54 -0
- tony_cli/payload.py +187 -0
- tony_cli/source/__init__.py +0 -0
- tony_cli/source/local.py +256 -0
- tony_cli/uninstall.py +200 -0
- tony_cli-0.3.0.dist-info/METADATA +181 -0
- tony_cli-0.3.0.dist-info/RECORD +24 -0
- tony_cli-0.3.0.dist-info/WHEEL +5 -0
- tony_cli-0.3.0.dist-info/entry_points.txt +2 -0
- tony_cli-0.3.0.dist-info/licenses/LICENSE +24 -0
- tony_cli-0.3.0.dist-info/top_level.txt +1 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
@font-face{font-family:'Favorit';font-weight:400;font-style:normal;font-display:swap;src:url('/fonts/Favorit-Regular.woff2') format('woff2')}
|
|
2
|
+
@font-face{font-family:'Favorit';font-weight:500;font-style:normal;font-display:swap;src:url('/fonts/Favorit-Book.woff2') format('woff2')}
|
|
3
|
+
@font-face{font-family:'Favorit';font-weight:700;font-style:normal;font-display:swap;src:url('/fonts/Favorit-Bold.woff2') format('woff2')}
|
|
4
|
+
@font-face{font-family:'Favorit Mono';font-weight:400;font-style:normal;font-display:swap;src:url('/fonts/FavoritMono-Regular.woff2') format('woff2')}
|
|
5
|
+
@font-face{font-family:'Favorit Mono';font-weight:500;font-style:normal;font-display:swap;src:url('/fonts/FavoritMono-Medium.woff2') format('woff2')}
|
|
6
|
+
/* Dark by design, not by preference: Behold's ground is #000 and the page commits to it.
|
|
7
|
+
Depth is a surface step plus a hairline step — never a shadow.
|
|
8
|
+
The accent means exactly one thing: changed. Selection and focus use contrast instead. */
|
|
9
|
+
:root {
|
|
10
|
+
--ground:#000000; --s1:#0a0a0a; --s2:#111110; --s3:#171614;
|
|
11
|
+
--rule:#1e1d1b; --rule-2:#2b2825; --rule-3:#3a3631;
|
|
12
|
+
--ink:#ffffff; --ink-2:#c9c5bf; --ink-3:#8f8b86; --ink-4:#5f5b55;
|
|
13
|
+
--accent:#c89f6d; --accent-line:#5c4a30; --accent-bg:#14100a;
|
|
14
|
+
--pos:#7fb069; --pos-bg:#0b1209; --neg:#ff8081; --neg-bg:#170b0c;
|
|
15
|
+
--info:#8fb3cc; --info-bg:#0c141c; --info-line:#1e3445;
|
|
16
|
+
--sans:'Favorit',system-ui,-apple-system,sans-serif;
|
|
17
|
+
--mono:'Favorit Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
|
|
18
|
+
}
|
|
19
|
+
*{box-sizing:border-box}
|
|
20
|
+
html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
|
|
21
|
+
body{margin:0;padding:0 2rem 6rem;background:var(--ground);color:var(--ink-2);
|
|
22
|
+
font:400 15px/1.55 var(--sans);font-variant-numeric:tabular-nums}
|
|
23
|
+
.wrap{max-width:76rem;margin:0 auto}
|
|
24
|
+
.mono,code{font-family:var(--mono)}
|
|
25
|
+
::selection{background:var(--rule-3);color:var(--ink)}
|
|
26
|
+
|
|
27
|
+
/* ---- chrome: the studio treatment ---- */
|
|
28
|
+
header{padding:2.75rem 0 1.25rem;border-bottom:1px solid var(--rule);margin-bottom:1.5rem}
|
|
29
|
+
.mh{display:flex;align-items:baseline;gap:1rem;flex-wrap:wrap;margin-bottom:1.5rem}
|
|
30
|
+
.brand{font-family:var(--mono);font-weight:500;font-size:.72rem;letter-spacing:.22em;
|
|
31
|
+
text-transform:uppercase;color:var(--ink)}
|
|
32
|
+
.mh .rng,.mh .repo{font-family:var(--mono);font-size:.7rem;letter-spacing:.02em;color:var(--ink-4)}
|
|
33
|
+
.mh .rng{color:var(--ink-3)}
|
|
34
|
+
.mh .repo{margin-left:auto;word-break:break-all}
|
|
35
|
+
h1{font-size:clamp(1.25rem,2.3vw,1.65rem);font-weight:700;letter-spacing:-.042em;
|
|
36
|
+
word-spacing:-.09em;line-height:1.2;margin:0 0 1.5rem;max-width:52ch;text-wrap:balance;
|
|
37
|
+
color:var(--ink)}
|
|
38
|
+
.meta{display:flex;flex-wrap:wrap;gap:.4rem 1.5rem;font-family:var(--mono);
|
|
39
|
+
font-size:.7rem;color:var(--ink-4);align-items:center}
|
|
40
|
+
.meta b{font-weight:500}
|
|
41
|
+
.meta .pos{color:var(--pos)} .meta .neg{color:var(--neg)}
|
|
42
|
+
|
|
43
|
+
/* Section headers and tabs are eyebrows: uppercase mono, positive tracking, bracketed. */
|
|
44
|
+
.tabs-main{display:flex;gap:0;border-bottom:1px solid var(--rule);margin-bottom:1.25rem}
|
|
45
|
+
.mt{font:500 .68rem/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;
|
|
46
|
+
background:none;border:0;border-bottom:1px solid transparent;color:var(--ink-4);
|
|
47
|
+
padding:.9rem 1.15rem;cursor:pointer;display:flex;align-items:center;gap:.6rem;
|
|
48
|
+
margin-bottom:-1px}
|
|
49
|
+
.mt:first-child{padding-left:0}
|
|
50
|
+
.mt::before{content:"[";color:var(--rule-3)}
|
|
51
|
+
.mt::after{content:"]";color:var(--rule-3)}
|
|
52
|
+
.mt:hover:not(:disabled){color:var(--ink-3)}
|
|
53
|
+
.mt[aria-selected="true"]{color:var(--ink);border-bottom-color:var(--ink)}
|
|
54
|
+
.mt[aria-selected="true"]::before,.mt[aria-selected="true"]::after{color:var(--ink-4)}
|
|
55
|
+
.mt:disabled{color:var(--rule-3);cursor:default}
|
|
56
|
+
.mt:focus-visible{outline:1px solid var(--rule-3);outline-offset:-1px}
|
|
57
|
+
.mt .c{font-size:.68rem;color:var(--ink-4);letter-spacing:.06em}
|
|
58
|
+
.mt[aria-selected="true"] .c{color:var(--ink-3)}
|
|
59
|
+
|
|
60
|
+
h2{font:500 .68rem/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;
|
|
61
|
+
color:var(--ink-3);margin:0 0 1rem}
|
|
62
|
+
.loose{margin-bottom:2.5rem;border-left:1px solid var(--neg);padding-left:1.25rem}
|
|
63
|
+
.loose ul{margin:0;padding-left:1.1rem}
|
|
64
|
+
.loose li{margin-bottom:.5rem;font-size:.875rem;color:var(--ink-2);max-width:60ch}
|
|
65
|
+
|
|
66
|
+
/* ---- file rows: hairlines, not cards ---- */
|
|
67
|
+
.file{border-bottom:1px solid var(--rule);background:var(--ground)}
|
|
68
|
+
.file:first-child{border-top:1px solid var(--rule)}
|
|
69
|
+
/* The changed-file header is a heading, not a control — the tree does the
|
|
70
|
+
opening. Blast radius still uses <details>, so the summary rules stay. */
|
|
71
|
+
.file .fhead,.file summary{display:flex;align-items:center;gap:.85rem;padding:.6rem .25rem;
|
|
72
|
+
font-family:var(--mono);font-size:.75rem;flex-wrap:wrap;list-style:none;color:var(--ink-3)}
|
|
73
|
+
.file summary{cursor:pointer}
|
|
74
|
+
.file .fhead{border-bottom:1px solid var(--rule);position:sticky;top:0;background:var(--ground);
|
|
75
|
+
z-index:1}
|
|
76
|
+
.file summary::-webkit-details-marker{display:none}
|
|
77
|
+
.file summary:hover{background:var(--s1)}
|
|
78
|
+
.file[open] summary{border-bottom:1px solid var(--rule)}
|
|
79
|
+
.ix{color:var(--ink-4);letter-spacing:.06em;font-size:.7rem;flex:none}
|
|
80
|
+
.st{font-size:.6rem;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-4);flex:none}
|
|
81
|
+
.st.added{color:var(--pos)}
|
|
82
|
+
.st.deleted{color:var(--neg)}
|
|
83
|
+
.fp{flex:1;min-width:0;word-break:break-all;color:var(--ink)}
|
|
84
|
+
.from{color:var(--ink-4);font-size:.7rem}
|
|
85
|
+
.nb{font-size:.62rem;color:var(--ink-3);border:1px solid var(--rule-2);padding:.2em .5em;
|
|
86
|
+
line-height:1}
|
|
87
|
+
.cnt{font-size:.7rem;white-space:nowrap;color:var(--ink-4)}
|
|
88
|
+
.pos{color:var(--pos)} .neg{color:var(--neg)} .z{color:var(--ink-4);font-weight:400}
|
|
89
|
+
|
|
90
|
+
/* ---- the dense interior ---- */
|
|
91
|
+
.hunk{overflow-x:auto;background:var(--s1);border-bottom:1px solid var(--rule)}
|
|
92
|
+
.l{font-family:var(--mono);font-size:.75rem;line-height:1.5;white-space:pre;
|
|
93
|
+
min-width:max-content;padding-right:1rem;color:var(--ink-2)}
|
|
94
|
+
.g{display:inline-block;width:3.4rem;padding-right:1rem;text-align:right;color:var(--ink-4);
|
|
95
|
+
user-select:none;-webkit-user-select:none}
|
|
96
|
+
.l.h{background:var(--s2);color:var(--ink-4);padding-top:.25rem;padding-bottom:.25rem;
|
|
97
|
+
border-top:1px solid var(--rule);border-bottom:1px solid var(--rule)}
|
|
98
|
+
.file[open] .hunk > .l.h:first-child{border-top:0}
|
|
99
|
+
.l.a{background:var(--pos-bg);color:var(--ink)}
|
|
100
|
+
.l.d{background:var(--neg-bg);color:var(--ink-3)}
|
|
101
|
+
.bin{margin:0;padding:.9rem .25rem;color:var(--ink-4);font-size:.8rem;
|
|
102
|
+
border-bottom:1px solid var(--rule)}
|
|
103
|
+
|
|
104
|
+
/* Annotations carry the accent because they mark changed code. Nothing else may. */
|
|
105
|
+
.ann{background:var(--accent-bg);border-top:1px solid var(--accent-line);
|
|
106
|
+
border-bottom:1px solid var(--accent-line);padding:.75rem 1rem .75rem 4.4rem;
|
|
107
|
+
white-space:normal;font-family:var(--sans);min-width:auto;position:sticky;left:0}
|
|
108
|
+
.ann.risk{background:var(--neg-bg);border-color:var(--neg-bg)}
|
|
109
|
+
.ann .t{margin:0 0 .5rem;font:500 .65rem/1.4 var(--mono);letter-spacing:.14em;
|
|
110
|
+
text-transform:uppercase;color:var(--accent);display:flex;align-items:center;
|
|
111
|
+
gap:.6rem;flex-wrap:wrap}
|
|
112
|
+
.ann.risk .t{color:var(--neg)}
|
|
113
|
+
.ann .ln{font-size:.62rem;letter-spacing:.06em;color:var(--ink-4);text-transform:none}
|
|
114
|
+
.ann .k{font-size:.58rem;letter-spacing:.1em;color:var(--ink-4)}
|
|
115
|
+
.ann .n{margin:0;font-size:.85rem;line-height:1.55;color:var(--ink-2);max-width:64ch}
|
|
116
|
+
.tabs{display:flex;gap:1rem;margin-bottom:.6rem}
|
|
117
|
+
.pt{font:500 .62rem/1 var(--mono);letter-spacing:.14em;text-transform:uppercase;
|
|
118
|
+
background:none;cursor:pointer;border:0;border-bottom:1px solid transparent;
|
|
119
|
+
padding:0 0 .25rem;color:var(--ink-4)}
|
|
120
|
+
.pt:hover{color:var(--ink-3)}
|
|
121
|
+
.pt[aria-selected="true"]{color:var(--ink);border-bottom-color:var(--ink-3)}
|
|
122
|
+
.pt:focus-visible{outline:1px solid var(--rule-3);outline-offset:2px}
|
|
123
|
+
.risks-off .ann.risk{display:none}
|
|
124
|
+
.toggle{display:flex;align-items:center;gap:.5rem;font-family:var(--mono);
|
|
125
|
+
font-size:.7rem;color:var(--ink-4);cursor:pointer;user-select:none}
|
|
126
|
+
.toggle:hover{color:var(--ink-3)}
|
|
127
|
+
.toggle input{accent-color:var(--ink-3);margin:0}
|
|
128
|
+
|
|
129
|
+
/* ---- blast radius ---- */
|
|
130
|
+
.impacts{margin-top:0}
|
|
131
|
+
.file.impacted .st.breaks{color:var(--neg)}
|
|
132
|
+
.file.impacted .st.behavior-change{color:var(--info)}
|
|
133
|
+
.file.impacted .st.compatible{color:var(--ink-4)}
|
|
134
|
+
.jumps{padding:.6rem .25rem;font-family:var(--mono);font-size:.68rem;color:var(--ink-4);
|
|
135
|
+
display:flex;gap:.6rem;flex-wrap:wrap;align-items:center;border-bottom:1px solid var(--rule)}
|
|
136
|
+
.jump{color:var(--info);text-decoration:none;border-bottom:1px solid transparent}
|
|
137
|
+
.jump:hover{border-bottom-color:currentColor}
|
|
138
|
+
.jump.breaks{color:var(--neg)}
|
|
139
|
+
.jump.compatible{color:var(--ink-3)}
|
|
140
|
+
.hunk.full{max-height:34rem;overflow-y:auto}
|
|
141
|
+
.l.hit{background:var(--s3)}
|
|
142
|
+
.l.focus{background:var(--s3);box-shadow:inset 2px 0 0 var(--ink-3)}
|
|
143
|
+
.ann.imp{background:var(--info-bg);border-color:var(--info-line)}
|
|
144
|
+
.ann.imp .t{color:var(--info)}
|
|
145
|
+
.ann.imp.breaks{background:var(--neg-bg);border-color:var(--neg-bg)}
|
|
146
|
+
.ann.imp.breaks .t{color:var(--neg)}
|
|
147
|
+
.ann.imp.compatible{background:var(--s2);border-color:var(--rule)}
|
|
148
|
+
.ann.imp.compatible .t{color:var(--ink-3)}
|
|
149
|
+
.ann .via{font-weight:400;text-transform:none;letter-spacing:.01em;color:var(--ink-4);
|
|
150
|
+
font-size:.68rem}
|
|
151
|
+
.stepper{position:sticky;top:0;z-index:4;display:flex;align-items:center;gap:.75rem;
|
|
152
|
+
background:var(--ground);border-bottom:1px solid var(--rule);padding:.7rem .25rem;
|
|
153
|
+
margin-bottom:0;font-family:var(--mono);font-size:.7rem;color:var(--ink-4)}
|
|
154
|
+
.stepper button{font:400 .8rem/1 var(--mono);background:none;border:1px solid var(--rule-2);
|
|
155
|
+
color:var(--ink-3);cursor:pointer;padding:.25em .6em}
|
|
156
|
+
.stepper button:hover{border-color:var(--rule-3);color:var(--ink)}
|
|
157
|
+
.stepper .sh{color:var(--ink-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
158
|
+
|
|
159
|
+
/* ---- walkthroughs ---- */
|
|
160
|
+
.wintro{border-left:1px solid var(--rule-2);padding:.1rem 0 .1rem 1.25rem;margin-bottom:1.25rem;
|
|
161
|
+
max-width:64ch}
|
|
162
|
+
.wintro p{margin:0 0 .6rem;font-size:.875rem;color:var(--ink-3)}
|
|
163
|
+
.wintro p:last-child{margin:0}
|
|
164
|
+
.wintro b{color:var(--ink-2);font-weight:500}
|
|
165
|
+
.tl{font-family:var(--mono);font-size:.6rem;letter-spacing:.14em;text-transform:uppercase;
|
|
166
|
+
color:var(--ink-4);margin-right:.6rem}
|
|
167
|
+
.wt{border-top:1px solid var(--rule);padding:1.25rem 0 2.5rem}
|
|
168
|
+
.wth{margin-bottom:1.5rem}
|
|
169
|
+
.cap{font:500 .62rem/1 var(--mono);letter-spacing:.16em;text-transform:uppercase;
|
|
170
|
+
color:var(--ink-4);margin:0 0 .8rem}
|
|
171
|
+
.wth h3{margin:0 0 .5rem;font-size:1.35rem;font-weight:700;letter-spacing:-.038em;
|
|
172
|
+
word-spacing:-.07em;color:var(--ink)}
|
|
173
|
+
.trig{margin:0;color:var(--ink-3);font-size:.9rem}
|
|
174
|
+
.wchg{margin:.5rem 0 0;font-size:.9rem;color:var(--ink-2)}
|
|
175
|
+
.covers{display:flex;gap:.6rem;flex-wrap:wrap;margin-top:1rem}
|
|
176
|
+
.chip{font-family:var(--mono);font-size:.62rem;letter-spacing:.06em;color:var(--ink-4);
|
|
177
|
+
border:1px solid var(--rule);padding:.2em .5em}
|
|
178
|
+
.chip.hot{color:var(--accent);border-color:var(--accent-line)}
|
|
179
|
+
.dots{display:flex;gap:.4rem;margin-bottom:1.25rem;flex-wrap:wrap}
|
|
180
|
+
.dot{font:400 .62rem/1 var(--mono);letter-spacing:.06em;min-width:2.1rem;height:1.9rem;
|
|
181
|
+
border:1px solid var(--rule);background:none;color:var(--ink-4);cursor:pointer;padding:0 .35em}
|
|
182
|
+
.dot:hover{border-color:var(--rule-3);color:var(--ink-3)}
|
|
183
|
+
.dot.new,.dot.changed{border-color:var(--accent-line);color:var(--accent)}
|
|
184
|
+
.dot.removed{border-style:dashed;text-decoration:line-through}
|
|
185
|
+
.dot[aria-current]{background:var(--ink);border-color:var(--ink);color:var(--ground)}
|
|
186
|
+
.dot:focus-visible{outline:1px solid var(--rule-3);outline-offset:2px}
|
|
187
|
+
.panels{display:grid;margin-bottom:1.25rem}
|
|
188
|
+
.stepPanel{grid-area:1 / 1;visibility:hidden}
|
|
189
|
+
.stepPanel.on{visibility:visible}
|
|
190
|
+
.say{margin:0 0 1rem;font-size:1.05rem;line-height:1.5;max-width:60ch;color:var(--ink);
|
|
191
|
+
letter-spacing:-.008em}
|
|
192
|
+
.ph{font-family:var(--mono);font-size:.58rem;letter-spacing:.14em;text-transform:uppercase;
|
|
193
|
+
margin-left:.7rem;color:var(--accent);white-space:nowrap;font-weight:500}
|
|
194
|
+
.ph.removed{color:var(--neg)}
|
|
195
|
+
.ph.same{display:none}
|
|
196
|
+
.split{display:grid;grid-template-columns:1fr 15rem;gap:1.25rem;align-items:start}
|
|
197
|
+
@media (max-width:52rem){.split{grid-template-columns:1fr}}
|
|
198
|
+
.cw{border:1px solid var(--rule);min-width:0;background:var(--s1)}
|
|
199
|
+
.cwh{font-family:var(--mono);font-size:.65rem;color:var(--ink-4);padding:.45rem .7rem;
|
|
200
|
+
background:var(--s2);border-bottom:1px solid var(--rule);word-break:break-all}
|
|
201
|
+
.cw .hunk{background:none;border:0}
|
|
202
|
+
.cw.none{padding:.9rem;color:var(--ink-4);font-size:.8rem;font-family:var(--mono)}
|
|
203
|
+
.l.hot{background:var(--s3);box-shadow:inset 2px 0 0 var(--rule-3);color:var(--ink)}
|
|
204
|
+
.state{border:1px solid var(--rule);padding:.8rem .85rem;background:var(--s1)}
|
|
205
|
+
.state .cap{margin:0 0 .7rem}
|
|
206
|
+
.sv{font-family:var(--mono);font-size:.7rem;margin-bottom:.7rem;line-height:1.5}
|
|
207
|
+
.sv:last-child{margin-bottom:0}
|
|
208
|
+
.sk{color:var(--ink-4);display:block;margin-bottom:.15rem}
|
|
209
|
+
.sv .was{color:var(--ink-4);text-decoration:line-through}
|
|
210
|
+
.sv .to{color:var(--ink-4);margin:0 .35em}
|
|
211
|
+
.sv .now{color:var(--accent)}
|
|
212
|
+
.wtnav{display:flex;align-items:center;gap:1rem;font-family:var(--mono);font-size:.7rem;
|
|
213
|
+
color:var(--ink-4)}
|
|
214
|
+
.wtnav button{font:500 .65rem/1 var(--mono);letter-spacing:.12em;text-transform:uppercase;
|
|
215
|
+
background:none;color:var(--ink-3);border:1px solid var(--rule-2);padding:.5rem .9rem;
|
|
216
|
+
cursor:pointer}
|
|
217
|
+
.wtnav button:hover:not(:disabled){border-color:var(--ink-3);color:var(--ink)}
|
|
218
|
+
.wtnav button:disabled{color:var(--rule-3);border-color:var(--rule);cursor:default}
|
|
219
|
+
|
|
220
|
+
/* Elided regions of a windowed file: the cut is announced, never silent. */
|
|
221
|
+
.l.elide{color:var(--ink-4);font-style:normal;background:var(--s2)}
|
|
222
|
+
|
|
223
|
+
/* Hosted-page states: loading / bad key. Same quiet chrome as everything else. */
|
|
224
|
+
.rstate{padding:4rem 0;font-family:var(--mono);font-size:.75rem;color:var(--ink-3)}
|
|
225
|
+
.rstate .err{color:var(--neg)}
|
|
226
|
+
|
|
227
|
+
/* ---- coverage gaps ----
|
|
228
|
+
The absence of an annotation, drawn as an absence: a dashed rule, no surface,
|
|
229
|
+
no accent. It must read as "nothing was said here", never as content. */
|
|
230
|
+
.gap{display:flex;align-items:center;gap:.75rem;margin:.35rem 0;padding:.4rem .9rem;
|
|
231
|
+
border:1px dashed var(--rule-2);color:var(--ink-4);font:400 .7rem/1 var(--mono)}
|
|
232
|
+
.gap .gl{color:var(--ink-3)}
|
|
233
|
+
.gap .gt{letter-spacing:.1em;text-transform:uppercase;font-size:.62rem}
|
|
234
|
+
.gb{font-size:.62rem;color:var(--neg);border:1px dashed var(--neg);padding:.2em .5em;
|
|
235
|
+
line-height:1;letter-spacing:.04em}
|
|
236
|
+
.gsum{color:var(--neg)}
|
|
237
|
+
.tg{flex:none;color:var(--neg);font-size:.5rem;line-height:1}
|
|
238
|
+
|
|
239
|
+
/* ---- file tree ----
|
|
240
|
+
A long review is a long page; this is the index into it. Same hairline-and-
|
|
241
|
+
surface grammar as everything else — the only colour is the change marker. */
|
|
242
|
+
.flayout{display:grid;grid-template-columns:minmax(0,17rem) minmax(0,1fr);gap:2rem;
|
|
243
|
+
align-items:start}
|
|
244
|
+
.fmain{min-width:0}
|
|
245
|
+
.tree{position:sticky;top:0;max-height:100vh;overflow-y:auto;overscroll-behavior:contain;
|
|
246
|
+
padding:1rem .25rem 2rem 0;border-right:1px solid var(--rule);min-width:0}
|
|
247
|
+
.tfil{position:sticky;top:0;background:var(--ground);padding-bottom:.6rem;z-index:1}
|
|
248
|
+
#treeFilter{width:100%;background:var(--s1);border:1px solid var(--rule-2);color:var(--ink);
|
|
249
|
+
font:400 .75rem/1 var(--mono);padding:.55rem .65rem;border-radius:0;
|
|
250
|
+
-webkit-appearance:none;appearance:none}
|
|
251
|
+
#treeFilter::placeholder{color:var(--ink-4)}
|
|
252
|
+
#treeFilter:focus{outline:none;border-color:var(--rule-3);background:var(--s2)}
|
|
253
|
+
.tn,.tsub{list-style:none;margin:0;padding:0}
|
|
254
|
+
.tsub{padding-left:.7rem;border-left:1px solid var(--rule);margin-left:.45rem}
|
|
255
|
+
.tdh,.tfb{display:flex;align-items:center;gap:.45rem;width:100%;background:none;border:0;
|
|
256
|
+
color:var(--ink-3);font:400 .75rem/1.4 var(--mono);text-align:left;padding:.32rem .4rem;
|
|
257
|
+
cursor:pointer;min-width:0}
|
|
258
|
+
.tdh:hover,.tfb:hover{background:var(--s1);color:var(--ink-2)}
|
|
259
|
+
.tdh:focus-visible,.tfb:focus-visible{outline:1px solid var(--rule-3);outline-offset:-1px}
|
|
260
|
+
.tdh{color:var(--ink-4);letter-spacing:.04em}
|
|
261
|
+
.tw{flex:none;width:.6rem;color:var(--rule-3);transition:transform .12s ease}
|
|
262
|
+
.tw::before{content:"\25be"}
|
|
263
|
+
.tdh[aria-expanded="false"] .tw{transform:rotate(-90deg)}
|
|
264
|
+
.tnm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
265
|
+
.tfb .tnm{color:var(--ink-2)}
|
|
266
|
+
.ti{flex:none;width:.7rem;text-align:center;color:var(--ink-4);font-weight:500}
|
|
267
|
+
.ti.added{color:var(--pos)}
|
|
268
|
+
.ti.deleted{color:var(--neg)}
|
|
269
|
+
.ti.renamed{color:var(--info)}
|
|
270
|
+
.tct{flex:none;font-size:.65rem;color:var(--ink-4);letter-spacing:.02em}
|
|
271
|
+
/* Current file: the accent means "changed" everywhere else, so selection is a
|
|
272
|
+
contrast step instead. */
|
|
273
|
+
.tfb[aria-current]{background:var(--s2);color:var(--ink)}
|
|
274
|
+
.tfb[aria-current] .tnm{color:var(--ink)}
|
|
275
|
+
.tnone{font:400 .72rem/1 var(--mono);color:var(--ink-4);padding:.6rem .4rem;margin:0}
|
|
276
|
+
|
|
277
|
+
@media (max-width:64rem){
|
|
278
|
+
.flayout{grid-template-columns:minmax(0,1fr)}
|
|
279
|
+
.tree{position:static;max-height:20rem;border-right:0;border-bottom:1px solid var(--rule);
|
|
280
|
+
padding:0 0 1rem;margin-bottom:1.25rem}
|
|
281
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";(()=>{var M={breaks:"Breaks","behavior-change":"Behaves differently",compatible:"Compatible"},T={breaks:0,"behavior-change":1,compatible:2},x={new:"new",changed:"changed",removed:"no longer happens",same:""},u=e=>String(e??"").replace(/[&<>"']/g,t=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[t]),g=(e,t=0)=>{let s=typeof e=="number"?e:Number(e);return Number.isFinite(s)?Math.trunc(s):t},w=e=>String(e??"").replace(/[^a-zA-Z0-9_-]/g,""),k=e=>e.replace(/[^A-Za-z0-9]/g,"_"),E=e=>String(e).padStart(2,"0");function S(e){if(!e)return"";let t=g(e[0]),s=g(e[1]);return`<span class="ln">${t===s?`line ${t}`:`lines ${t}\u2013${s}`}</span>`}var R=[["prev","Prev"],["now","New"],["impact","Changes"]],B=0;function W(e){return`<div class="gap"><span class="gl">${S(e)}</span><span class="gt">not explained</span></div>`}function F(e,t,s,d){if(t==="risk")return`<div class="ann risk"><p class="t">Potential risk${S(s)}</p><p class="n">${u(e.text)}</p></div>`;let o=u(e.title||"Note"),n=R.filter(([l])=>e[l]).map(([l,c])=>({key:l,label:c,text:e[l]}));if(n.length<=1){let l=n[0]?.text??e.note??"";return`<div class="ann"><p class="t">${o}${S(s)}</p><p class="n">${u(l)}</p></div>`}let p=++B,$=n.map((l,c)=>`<button class="pt" data-g="${p}" data-p="${l.key}" aria-selected="${c===0}">${l.label}</button>`).join(""),a=n.map((l,c)=>`<p class="n pane" data-g="${p}" data-p="${l.key}"${c===0?"":" hidden"}>${u(l.text)}</p>`).join("");return`<div class="ann"><p class="t">${o}<span class="k">${u(d.toUpperCase())}</span>${S(s)}</p><div class="tabs">${$}</div>${a}</div>`}function z(e,t,s){return e.map((d,o)=>{let n=o+1,p=d.blocks??[],$=p.filter(r=>r.k==="note"||r.k==="risk").length,a=p.filter(r=>r.k==="gap").length,l=(r,i)=>i[r]??{},c;d.binary?c='<p class="bin">Binary \u2014 not shown.</p>':p.length===0?c='<p class="bin">No textual changes.</p>':c=`<div class="hunk">${p.map(i=>i.k==="row"?`<div class="l ${w(i.cls)}"><span class="g">${i.g==null?"":g(i.g)}</span>${u(i.text)||" "}</div>`:i.k==="gap"?W(i.span):F(l(g(i.n),i.k==="risk"?s:t),i.k==="risk"?"risk":"note",i.span,String(i.tag??""))).join("")}</div>`;let f=d.oldPath?`<span class="from">from ${u(d.oldPath)}</span>`:"",h=$>0?`<span class="nb">${$}</span>`:"",m=g(d.unexplainedLines)>0?`<span class="gb">${g(d.unexplainedLines)} lines unexplained</span>`:"";return`
|
|
2
|
+
<section class="file" id="file-${k(d.path)}"${o===0?"":" hidden"}>
|
|
3
|
+
<div class="fhead">
|
|
4
|
+
<span class="ix">[${E(n)}]</span>
|
|
5
|
+
<span class="st ${u(d.status)}">${u(String(d.status).slice(0,3))}</span>
|
|
6
|
+
<span class="fp">${u(d.path)}</span>${f}
|
|
7
|
+
${h}${m}
|
|
8
|
+
<span class="cnt"><b class="${g(d.additions)?"pos":"z"}">+${g(d.additions)}</b> <b class="${g(d.deletions)?"neg":"z"}">−${g(d.deletions)}</b></span>
|
|
9
|
+
</div>
|
|
10
|
+
${c}
|
|
11
|
+
</section>`}).join("")}var D={added:"+",deleted:"\u2212",renamed:"\u2192"};function O(e){let t={name:"",dir:!0,children:[]};e.forEach((o,n)=>{let p=String(o?.path??"").split("/").filter(Boolean);if(p.length===0)return;let $=t;p.slice(0,-1).forEach(a=>{let l=$.children.find(c=>c.dir&&c.name===a);l||(l={name:a,dir:!0,children:[]},$.children.push(l)),$=l}),$.children.push({name:p[p.length-1],dir:!1,children:[],file:o,index:n+1})});let s=o=>{for(o.children=o.children.map(s);o.dir&&o.children.length===1&&o.children[0].dir;){let n=o.children[0];o.name=`${o.name}/${n.name}`,o.children=n.children}return o},d=o=>(o.forEach(n=>d(n.children)),o.sort((n,p)=>n.dir===p.dir?n.name.localeCompare(p.name):n.dir?-1:1),o);return d(t.children.map(s))}function H(e){return e.map(t=>{if(t.dir)return`
|
|
12
|
+
<li class="td">
|
|
13
|
+
<button class="tdh" type="button" aria-expanded="true">
|
|
14
|
+
<span class="tw" aria-hidden="true"></span><span class="tnm">${u(t.name)}</span>
|
|
15
|
+
</button>
|
|
16
|
+
<ul class="tsub">${H(t.children)}</ul>
|
|
17
|
+
</li>`;let s=t.file??{},d=String(s.status??""),o=D[d]??"\xB1";return`
|
|
18
|
+
<li class="tfl" data-search="${u(String(s.path??"").toLowerCase())}">
|
|
19
|
+
<button class="tfb" type="button" data-target="file-${k(String(s.path??""))}"${t.index===1?" aria-current":""}>
|
|
20
|
+
<span class="ti ${w(d)}" aria-hidden="true">${o}</span>
|
|
21
|
+
<span class="tnm">${u(t.name)}</span>
|
|
22
|
+
${g(s.unexplainedLines)?`<span class="tg" title="${g(s.unexplainedLines)} lines unexplained">\u25CF</span>`:""}
|
|
23
|
+
<span class="tct"><b class="${g(s.additions)?"pos":"z"}">+${g(s.additions)}</b> <b class="${g(s.deletions)?"neg":"z"}">−${g(s.deletions)}</b></span>
|
|
24
|
+
</button>
|
|
25
|
+
</li>`}).join("")}function _(e){let t=O(e);return t.length===0?"":`
|
|
26
|
+
<aside class="tree" aria-label="Changed files">
|
|
27
|
+
<div class="tfil">
|
|
28
|
+
<input type="search" id="treeFilter" placeholder="Filter files\u2026" aria-label="Filter files" autocomplete="off">
|
|
29
|
+
</div>
|
|
30
|
+
<ul class="tn">${H(t)}</ul>
|
|
31
|
+
<p class="tnone" hidden>No files match.</p>
|
|
32
|
+
</aside>`}function U(e){let t=e.kind??"behavior-change",s="";return e.symbol&&(s=`<span class="via">via <code>${u(e.symbol)}</code>${e.fromPath?` in ${u(e.fromPath)}`:""}</span>`),`<div class="ann imp ${u(t)}"><p class="t">${u(M[t]??t)}${s}</p><p class="n">${u(e.why)}</p></div>`}function G(e,t){let s=new Map;for(let n of e)n.path&&(s.has(n.path)||s.set(n.path,[]),s.get(n.path).push(n));let d=n=>Math.min(...n.map(p=>T[p.kind]??1));return[...s.entries()].sort((n,p)=>d(n[1])-d(p[1])).map(([n,p],$)=>{let a=$+1,l=Object.keys(T).find(r=>T[r]===d(p)),c=t[n]??null,f=new Map;for(let r of p){let i=g(r.line,1);f.has(i)||f.set(i,[]),f.get(i).push(r)}let h=p.slice().sort((r,i)=>(r.line??1)-(i.line??1)).map(r=>`<a class="jump ${w(r.kind)}" href="#imp-${k(n)}-${g(r.line,1)}">line ${g(r.line,1)}</a>`).join(" "),m;if(!c)m='<p class="bin">Source not available for this file.</p>';else{let r=g(c.start,1),i=Math.max(0,r-1),b=r+c.lines.length-1,v=Math.max(0,g(c.total)-b),y=[];i>0&&y.push(`<div class="l c elide"><span class="g"></span>\u2026 ${i} earlier line${i===1?"":"s"}</div>`),c.lines.forEach((I,j)=>{let L=r+j;for(let C of f.get(L)??[])y.push(U(C));let P=f.has(L)?" hit":"";y.push(`<div class="l c${P}" id="imp-${k(n)}-${L}"><span class="g">${L}</span>${u(I)||" "}</div>`)}),v>0&&y.push(`<div class="l c elide"><span class="g"></span>\u2026 ${v} later line${v===1?"":"s"}</div>`),m=y.join("")}return`
|
|
33
|
+
<details class="file impacted ${l}" id="impact-${k(n)}"${a===1?" open":""}>
|
|
34
|
+
<summary>
|
|
35
|
+
<span class="ix">[${E(a)}]</span>
|
|
36
|
+
<span class="st ${l}">${u(M[l]??l)}</span>
|
|
37
|
+
<span class="fp">${u(n)}</span>
|
|
38
|
+
<span class="nb">${p.length}</span>
|
|
39
|
+
<span class="cnt">not edited</span>
|
|
40
|
+
</summary>
|
|
41
|
+
<div class="jumps">${p.length} impact site${p.length===1?"":"s"}: ${h}</div>
|
|
42
|
+
<div class="hunk full">${m}</div>
|
|
43
|
+
</details>`}).join("")}function K(e){let t=e.window??null;if(!t)return'<div class="cw none">happens outside the codebase</div>';let s=g(t.hot?.[0],0),d=g(t.hot?.[1],-1),o=t.lines.map((n,p)=>{let $=g(t.start,1)+p;return`<div class="l c${$>=s&&$<=d?" hot":""}"><span class="g">${$}</span>${u(n)||" "}</div>`}).join("");return`<div class="cw"><div class="cwh">${u(e.path)}</div><div class="hunk">${o}</div></div>`}function V(e){let t=Object.entries(e??{}).slice(0,3);return t.length===0?"":`<div class="state"><p class="cap">[ state ]</p>${t.map(([d,o])=>{let n=String(o);if(n.includes("->")){let[p,,$]=(()=>{let a=n.indexOf("->");return[n.slice(0,a),"->",n.slice(a+2)]})();return`<div class="sv"><span class="sk">${u(d)}</span><span class="was">${u(p.trim())}</span><span class="to">\u2192</span><span class="now">${u($.trim())}</span></div>`}return`<div class="sv"><span class="sk">${u(d)}</span><span class="now">${u(n)}</span></div>`}).join("")}</div>`}function Z(e){return e.length===0?"":'<div class="wintro"><p><b>These are traces, not diagrams.</b> Each one follows a single real scenario from the moment it starts, one step at a time. The code shown is read straight from your files.</p><p>Use <b>next</b> to advance. Before you press it, say what you think happens next \u2014 that guess is what makes the step stick.</p></div>'+e.map((s,d)=>{let o=s.steps??[];if(o.length===0)return"";let n=[];for(let h of o)h.path&&!n.includes(h.path)&&n.push(h.path);let p=o.filter(h=>(h.phase||"same")!=="same").length,$=e.length>1?` / ${E(e.length)}`:"",a=n.map(h=>`<span class="chip">${u(h.split("/").pop())}</span>`).join("")+(p>0?`<span class="chip hot">${p} of ${o.length} steps are new</span>`:""),l=s.whatChanged?`<p class="wchg"><span class="tl">What changed</span> ${u(s.whatChanged)}</p>`:"",c=o.map((h,m)=>{let r=h.phase||"same";return`<button class="dot ${w(r)}" data-w="${d}" data-s="${m}" aria-label="Step ${m+1}"${m===0?' aria-current="true"':""}>${E(m+1)}</button>`}).join(""),f=o.map((h,m)=>{let r=h.phase||"same",i=x[r]?`<span class="ph ${w(r)}">${x[r]}</span>`:"";return`<div class="stepPanel${m===0?" on":""}" data-w="${d}" data-s="${m}"><p class="say">${u(h.say)}${i}</p><div class="split">${K(h)}${V(h.state)}</div></div>`}).join("");return`
|
|
44
|
+
<section class="wt" data-w="${d}" data-n="${o.length}">
|
|
45
|
+
<header class="wth">
|
|
46
|
+
<p class="cap">[ walkthrough ${E(d+1)}${$} ]</p>
|
|
47
|
+
<h3>${u(s.title||"Walkthrough")}</h3>
|
|
48
|
+
<p class="trig"><span class="tl">Starts when</span> ${u(s.trigger)}</p>
|
|
49
|
+
${l}
|
|
50
|
+
<div class="covers">${a}</div>
|
|
51
|
+
</header>
|
|
52
|
+
<div class="dots">${c}</div>
|
|
53
|
+
<div class="panels">${f}</div>
|
|
54
|
+
<div class="wtnav">
|
|
55
|
+
<button class="wprev" data-w="${d}" disabled>‹ back</button>
|
|
56
|
+
<span class="wpos" data-w="${d}">step 1 of ${o.length}</span>
|
|
57
|
+
<button class="wnext" data-w="${d}">next ›</button>
|
|
58
|
+
</div>
|
|
59
|
+
</section>`}).join("")}function N(e,t){let s=t.files??[],d=t.annotations??[],o=t.risks??[],n=(t.impacts??[]).filter(i=>i.path),p=t.walkthroughs??[],$=t.impactWindows??{},a=new Set(n.map(i=>i.path)).size,l=s.reduce((i,b)=>i+g(b.additions),0),c=s.reduce((i,b)=>i+g(b.deletions),0),f=o.filter(i=>!i.path),h=g(t.coverage?.unexplainedLines),m=Math.round(100*h/Math.max(g(t.coverage?.changedLines),1)),r=f.length?`<section class="loose"><h2>Risks outside the diff</h2><ul>${f.map(i=>`<li>${u(i.text)}</li>`).join("")}</ul></section>`:"";e.innerHTML=`
|
|
60
|
+
<div class="wrap">
|
|
61
|
+
<header>
|
|
62
|
+
<div class="mh">
|
|
63
|
+
<span class="brand">tony</span>
|
|
64
|
+
<span class="rng">${u(t.range)}</span>
|
|
65
|
+
<span class="repo">${u(t.repo)}</span>
|
|
66
|
+
</div>
|
|
67
|
+
<h1>${u(t.intent||"No summary produced.")}</h1>
|
|
68
|
+
<div class="meta">
|
|
69
|
+
<span>${s.length} files</span>
|
|
70
|
+
<span><b class="pos">+${l}</b> <b class="neg">−${c}</b></span>
|
|
71
|
+
<span>${d.length} annotations</span>
|
|
72
|
+
<label class="toggle"><input type="checkbox" id="riskToggle"> potential risks (${o.length})</label>
|
|
73
|
+
${h>0?`<span class="gsum">${h} of ${g(t.coverage?.changedLines)} changed lines unexplained (${m}%)</span>`:""}
|
|
74
|
+
</div>
|
|
75
|
+
</header>
|
|
76
|
+
${r}
|
|
77
|
+
<nav class="tabs-main">
|
|
78
|
+
<button class="mt" data-t="files" aria-selected="true">File changes <span class="c">${s.length}</span></button>
|
|
79
|
+
<button class="mt" data-t="blast" aria-selected="false"${a?"":" disabled"}>Blast radius <span class="c">${a}</span></button>
|
|
80
|
+
<button class="mt" data-t="walk" aria-selected="false"${p.length?"":" disabled"}>How it works <span class="c">${p.length}</span></button>
|
|
81
|
+
</nav>
|
|
82
|
+
<div id="pane-files">
|
|
83
|
+
<div class="flayout">
|
|
84
|
+
${_(s)}
|
|
85
|
+
<div class="fmain">${z(s,d,o)}</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div id="pane-blast" hidden>
|
|
89
|
+
<div class="stepper" id="stepper">
|
|
90
|
+
<button id="prevImp" aria-label="Previous impact">‹</button>
|
|
91
|
+
<span id="impPos">impact 1 of ${n.length}</span>
|
|
92
|
+
<button id="nextImp" aria-label="Next impact">›</button>
|
|
93
|
+
<span class="sh" id="impWhere"></span>
|
|
94
|
+
</div>
|
|
95
|
+
${G(n,$)}
|
|
96
|
+
</div>
|
|
97
|
+
<div id="pane-walk" hidden>${Z(p)}</div>
|
|
98
|
+
</div>`,J(e)}function J(e){let t=a=>e.querySelector(`#${a}`);e.querySelectorAll(".mt").forEach(a=>a.addEventListener("click",()=>{let l=a.dataset.t;e.querySelectorAll(".mt").forEach(c=>c.setAttribute("aria-selected",String(c.dataset.t===l))),t("pane-files").hidden=l!=="files",t("pane-blast").hidden=l!=="blast",t("pane-walk").hidden=l!=="walk"}));let s=e.querySelector(".tree");if(s){let a=Array.from(s.querySelectorAll(".tfb")),l=Array.from(e.querySelectorAll("#pane-files .file")),c=Math.max(0,a.findIndex(r=>r.hasAttribute("aria-current"))),f=r=>{let i=a[r];if(!i)return;let b=i.dataset.target;l.forEach(v=>{v.hidden=v.id!==b}),a.forEach((v,y)=>v.toggleAttribute("aria-current",y===r)),c=r,e.querySelector("#pane-files")?.scrollIntoView?.({block:"start"})};a.forEach((r,i)=>{r.addEventListener("click",()=>f(i))}),document.addEventListener("keydown",r=>{if(t("pane-files").hidden)return;let i=r.target;i&&i.tagName==="INPUT"||(r.key==="]"&&f(Math.min(a.length-1,c+1)),r.key==="["&&f(Math.max(0,c-1)))}),s.querySelectorAll(".tdh").forEach(r=>{r.addEventListener("click",()=>{let i=r.getAttribute("aria-expanded")==="true";r.setAttribute("aria-expanded",String(!i));let b=r.nextElementSibling;b&&(b.hidden=i)})});let h=s.querySelector("#treeFilter"),m=s.querySelector(".tnone");h?.addEventListener("input",()=>{let r=h.value.trim().toLowerCase(),i=0;if(s.querySelectorAll(".tfl").forEach(b=>{let v=!r||(b.dataset.search??"").includes(r);b.hidden=!v,v&&i++}),s.querySelectorAll(".td").forEach(b=>{let v=b.querySelector(".tfl:not([hidden])")!==null;if(b.hidden=!v,r&&v){b.querySelector(".tdh")?.setAttribute("aria-expanded","true");let y=b.querySelector(".tsub");y&&(y.hidden=!1)}}),m&&(m.hidden=i>0),i>0&&a[c]?.parentElement?.hidden){let b=a.findIndex(v=>!v.parentElement?.hidden);b>=0&&f(b)}})}e.querySelectorAll(".wt").forEach(a=>{let l=Number(a.dataset.n),c=0,f=()=>{a.querySelectorAll(".stepPanel").forEach(m=>m.classList.toggle("on",Number(m.dataset.s)===c)),a.querySelectorAll(".dot").forEach(m=>m.toggleAttribute("aria-current",Number(m.dataset.s)===c)),a.querySelector(".wpos").textContent=`step ${c+1} of ${l}`,a.querySelector(".wprev").disabled=c===0,a.querySelector(".wnext").disabled=c===l-1},h=m=>{c=Math.max(0,Math.min(l-1,m)),f()};a.querySelector(".wprev").onclick=()=>h(c-1),a.querySelector(".wnext").onclick=()=>h(c+1),a.querySelectorAll(".dot").forEach(m=>{m.onclick=()=>h(Number(m.dataset.s))}),f()});function d(a){let l=a.querySelector(".hunk.full"),c=a.querySelector(".l.hit");l&&c&&(l.scrollTop=Math.max(0,c.offsetTop-l.clientHeight/3))}e.querySelectorAll("#pane-blast details.impacted").forEach(a=>{a.open&&d(a),a.addEventListener("toggle",()=>{a.open&&d(a)})});let o=[...e.querySelectorAll("#pane-blast .l.hit")],n=-1;function p(a){if(!o.length)return;n=(a+o.length)%o.length;let l=o[n];l.closest("details").open=!0,o.forEach(f=>f.classList.remove("focus")),l.classList.add("focus"),l.scrollIntoView({behavior:"smooth",block:"center"}),t("impPos").textContent=`impact ${n+1} of ${o.length}`;let c=l.closest("details").querySelector(".fp");t("impWhere").textContent=c?c.textContent:""}t("prevImp")?.addEventListener("click",()=>p(n-1)),t("nextImp")?.addEventListener("click",()=>p(n+1)),document.addEventListener("keydown",a=>{t("pane-blast").hidden||(a.key==="]"&&p(n+1),a.key==="["&&p(n-1))}),e.addEventListener("click",a=>{let l=a.target.closest(".pt");if(!l)return;let c=l.dataset.g,f=l.dataset.p;e.querySelectorAll(`.pt[data-g="${c}"]`).forEach(h=>h.setAttribute("aria-selected",String(h.dataset.p===f))),e.querySelectorAll(`.pane[data-g="${c}"]`).forEach(h=>{h.hidden=h.dataset.p!==f})});let $=t("riskToggle");document.body.classList.add("risks-off"),$.addEventListener("change",()=>document.body.classList.toggle("risks-off",!$.checked))}var q=document.getElementById("tony-root"),A=document.getElementById("tony-payload");q&&A&&N(q,JSON.parse(A.textContent||"{}"));})();
|
tony_cli/fonts.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""ABC Favorit, embedded so a review page is one file that works offline.
|
|
2
|
+
|
|
3
|
+
The faces ship with the package as woff2 and are base64'd into the page at render
|
|
4
|
+
time. Five faces, ~300KB encoded — the cost of the page being self-contained.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import base64
|
|
8
|
+
import functools
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
FONT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", "fonts")
|
|
12
|
+
|
|
13
|
+
# family, weight, file. Book (500) is the emphasis weight — the step below Bold.
|
|
14
|
+
FACES = [
|
|
15
|
+
("Favorit", 400, "Favorit-Regular.woff2"),
|
|
16
|
+
("Favorit", 500, "Favorit-Book.woff2"),
|
|
17
|
+
("Favorit", 700, "Favorit-Bold.woff2"),
|
|
18
|
+
("Favorit Mono", 400, "FavoritMono-Regular.woff2"),
|
|
19
|
+
("Favorit Mono", 500, "FavoritMono-Medium.woff2"),
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@functools.lru_cache(maxsize=1)
|
|
24
|
+
def fontFaces():
|
|
25
|
+
"""The @font-face block. Missing files degrade to the fallback stack, silently."""
|
|
26
|
+
out = []
|
|
27
|
+
for family, weight, name in FACES:
|
|
28
|
+
path = os.path.join(FONT_DIR, name)
|
|
29
|
+
try:
|
|
30
|
+
with open(path, "rb") as fh:
|
|
31
|
+
data = base64.b64encode(fh.read()).decode("ascii")
|
|
32
|
+
except OSError:
|
|
33
|
+
continue
|
|
34
|
+
out.append(
|
|
35
|
+
f"@font-face{{font-family:'{family}';font-weight:{weight};font-style:normal;"
|
|
36
|
+
f"font-display:swap;src:url(data:font/woff2;base64,{data}) format('woff2')}}"
|
|
37
|
+
)
|
|
38
|
+
return "\n".join(out)
|