@drunkcoding/agents-and-skills 0.0.44 → 0.0.46

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.
Files changed (107) hide show
  1. package/.claude-plugin/marketplace.json +2 -47
  2. package/README.md +3 -6
  3. package/package.json +4 -1
  4. package/plugins/multica-tool/.claude-plugin/plugin.json +1 -1
  5. package/plugins/multica-tool/scripts/lib.mjs +53 -4
  6. package/plugins/multica-tool/scripts/multica-export.mjs +170 -50
  7. package/plugins/multica-tool/scripts/multica-import.mjs +209 -27
  8. package/plugins/multica-tool/skills/export/SKILL.md +91 -22
  9. package/plugins/multica-tool/skills/import/SKILL.md +24 -9
  10. package/plugins/multica-tool/skills/sync/SKILL.md +1 -1
  11. package/plugins/plugin-validator/.claude-plugin/plugin.json +1 -1
  12. package/plugins/html-effectiveness/.claude-plugin/plugin.json +0 -17
  13. package/plugins/html-effectiveness/README.md +0 -28
  14. package/plugins/html-effectiveness/agents/report-builder.md +0 -66
  15. package/plugins/html-effectiveness/assets/base.css +0 -51
  16. package/plugins/html-effectiveness/assets/base.js +0 -11
  17. package/plugins/html-effectiveness/assets/charts.js +0 -1
  18. package/plugins/html-effectiveness/assets/components.css +0 -10
  19. package/plugins/html-effectiveness/commands/html-report.md +0 -13
  20. package/plugins/html-effectiveness/docs/template-gallery.md +0 -28
  21. package/plugins/html-effectiveness/package.json +0 -6
  22. package/plugins/html-effectiveness/scripts/_tmp.js +0 -37
  23. package/plugins/html-effectiveness/scripts/catalog.js +0 -47
  24. package/plugins/html-effectiveness/scripts/extract.js +0 -104
  25. package/plugins/html-effectiveness/scripts/mustache.js +0 -90
  26. package/plugins/html-effectiveness/scripts/render.js +0 -176
  27. package/plugins/html-effectiveness/skills/html-effectiveness/SKILL.md +0 -94
  28. package/plugins/html-effectiveness/templates/01-exploration-code-approaches.html.tmpl +0 -400
  29. package/plugins/html-effectiveness/templates/02-exploration-visual-designs.html.tmpl +0 -456
  30. package/plugins/html-effectiveness/templates/03-code-review-pr.html.tmpl +0 -581
  31. package/plugins/html-effectiveness/templates/04-code-understanding.html.tmpl +0 -443
  32. package/plugins/html-effectiveness/templates/05-design-system.html.tmpl +0 -578
  33. package/plugins/html-effectiveness/templates/06-component-variants.html.tmpl +0 -557
  34. package/plugins/html-effectiveness/templates/07-prototype-animation.html.tmpl +0 -417
  35. package/plugins/html-effectiveness/templates/08-prototype-interaction.html.tmpl +0 -363
  36. package/plugins/html-effectiveness/templates/09-slide-deck.html.tmpl +0 -535
  37. package/plugins/html-effectiveness/templates/10-svg-illustrations.html.tmpl +0 -444
  38. package/plugins/html-effectiveness/templates/11-status-report.html.tmpl +0 -152
  39. package/plugins/html-effectiveness/templates/12-incident-report.html.tmpl +0 -547
  40. package/plugins/html-effectiveness/templates/13-flowchart-diagram.html.tmpl +0 -361
  41. package/plugins/html-effectiveness/templates/14-research-feature-explainer.html.tmpl +0 -348
  42. package/plugins/html-effectiveness/templates/15-research-concept-explainer.html.tmpl +0 -334
  43. package/plugins/html-effectiveness/templates/16-implementation-plan.html.tmpl +0 -657
  44. package/plugins/html-effectiveness/templates/17-pr-writeup.html.tmpl +0 -553
  45. package/plugins/html-effectiveness/templates/18-editor-triage-board.html.tmpl +0 -541
  46. package/plugins/html-effectiveness/templates/19-editor-feature-flags.html.tmpl +0 -627
  47. package/plugins/html-effectiveness/templates/20-editor-prompt-tuner.html.tmpl +0 -687
  48. package/plugins/html-effectiveness/templates/manifest.json +0 -441
  49. package/plugins/team-share/.claude-plugin/plugin.json +0 -18
  50. package/plugins/team-share/README.md +0 -46
  51. package/plugins/team-share/agents/team-share.md +0 -53
  52. package/plugins/team-share/commands/team-share.md +0 -4
  53. package/plugins/team-share/skills/claude-config/SKILL.md +0 -143
  54. package/plugins/team-share/skills/codegraph-setup/SKILL.md +0 -81
  55. package/plugins/team-share/skills/understand-setup/SKILL.md +0 -115
  56. package/plugins/tech-graph/.claude-plugin/plugin.json +0 -17
  57. package/plugins/tech-graph/README.md +0 -66
  58. package/plugins/tech-graph/agents/tech-graph.md +0 -97
  59. package/plugins/tech-graph/commands/tech-graph.md +0 -13
  60. package/plugins/tech-graph/scripts/check-deps.sh +0 -55
  61. package/plugins/tech-graph/skills/tech-graph/LICENSE +0 -21
  62. package/plugins/tech-graph/skills/tech-graph/README.md +0 -555
  63. package/plugins/tech-graph/skills/tech-graph/README.zh.md +0 -514
  64. package/plugins/tech-graph/skills/tech-graph/SKILL.md +0 -263
  65. package/plugins/tech-graph/skills/tech-graph/agents/openai.yaml +0 -4
  66. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style1-flat.png +0 -0
  67. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style2-dark.png +0 -0
  68. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
  69. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style4-notion.png +0 -0
  70. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style5-glass.png +0 -0
  71. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style6-claude.png +0 -0
  72. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style7-openai.png +0 -0
  73. package/plugins/tech-graph/skills/tech-graph/fixtures/agent-memory-types-style4.json +0 -181
  74. package/plugins/tech-graph/skills/tech-graph/fixtures/api-flow-style7.json +0 -40
  75. package/plugins/tech-graph/skills/tech-graph/fixtures/mem0-style1.json +0 -297
  76. package/plugins/tech-graph/skills/tech-graph/fixtures/microservices-style3.json +0 -64
  77. package/plugins/tech-graph/skills/tech-graph/fixtures/multi-agent-style5.json +0 -45
  78. package/plugins/tech-graph/skills/tech-graph/fixtures/system-architecture-style6.json +0 -48
  79. package/plugins/tech-graph/skills/tech-graph/fixtures/tool-call-style2.json +0 -182
  80. package/plugins/tech-graph/skills/tech-graph/package.json +0 -42
  81. package/plugins/tech-graph/skills/tech-graph/references/diagram-types.md +0 -171
  82. package/plugins/tech-graph/skills/tech-graph/references/icons.md +0 -406
  83. package/plugins/tech-graph/skills/tech-graph/references/style-1-flat-icon.md +0 -108
  84. package/plugins/tech-graph/skills/tech-graph/references/style-2-dark-terminal.md +0 -107
  85. package/plugins/tech-graph/skills/tech-graph/references/style-3-blueprint.md +0 -113
  86. package/plugins/tech-graph/skills/tech-graph/references/style-4-notion-clean.md +0 -95
  87. package/plugins/tech-graph/skills/tech-graph/references/style-5-glassmorphism.md +0 -125
  88. package/plugins/tech-graph/skills/tech-graph/references/style-6-claude-official.md +0 -209
  89. package/plugins/tech-graph/skills/tech-graph/references/style-7-openai.md +0 -215
  90. package/plugins/tech-graph/skills/tech-graph/references/style-diagram-matrix.md +0 -135
  91. package/plugins/tech-graph/skills/tech-graph/references/svg-layout-best-practices.md +0 -100
  92. package/plugins/tech-graph/skills/tech-graph/references/svg-to-png.md +0 -115
  93. package/plugins/tech-graph/skills/tech-graph/scripts/README.md +0 -269
  94. package/plugins/tech-graph/skills/tech-graph/scripts/generate-diagram.sh +0 -181
  95. package/plugins/tech-graph/skills/tech-graph/scripts/generate-from-template.py +0 -1587
  96. package/plugins/tech-graph/skills/tech-graph/scripts/test-all-styles.sh +0 -143
  97. package/plugins/tech-graph/skills/tech-graph/scripts/validate-svg.sh +0 -306
  98. package/plugins/tech-graph/skills/tech-graph/templates/agent-architecture.svg +0 -28
  99. package/plugins/tech-graph/skills/tech-graph/templates/architecture.svg +0 -23
  100. package/plugins/tech-graph/skills/tech-graph/templates/comparison-matrix.svg +0 -14
  101. package/plugins/tech-graph/skills/tech-graph/templates/data-flow.svg +0 -28
  102. package/plugins/tech-graph/skills/tech-graph/templates/er-diagram.svg +0 -21
  103. package/plugins/tech-graph/skills/tech-graph/templates/flowchart.svg +0 -21
  104. package/plugins/tech-graph/skills/tech-graph/templates/sequence.svg +0 -20
  105. package/plugins/tech-graph/skills/tech-graph/templates/state-machine.svg +0 -20
  106. package/plugins/tech-graph/skills/tech-graph/templates/timeline.svg +0 -19
  107. package/plugins/tech-graph/skills/tech-graph/templates/use-case.svg +0 -21
@@ -1,627 +0,0 @@
1
- <!--
2
- template: 19-editor-feature-flags
3
- upstream: 19-editor-feature-flags.html
4
- slots:
5
- title: string # replaces first <h1> innerHTML
6
- -->
7
- <div class="wrap">
8
-
9
- <header>
10
- <div class="eyebrow">Acme / editor / feature-flags</div>
11
- <h1>{{title}}</h1>
12
- <p class="sub">A form-based editor for the production feature-flag config.
13
- Toggle flags, fix dependency warnings as they surface, then copy out
14
- only the lines that changed.</p>
15
- </header>
16
-
17
- <div class="layout">
18
- <div class="banner" id="banner">
19
- <span class="glyph">&#9888;</span>
20
- <span id="bannerText">1 flag is enabled without its prerequisite</span>
21
- </div>
22
-
23
- <div class="form-col" id="formCol"><!-- groups injected --></div>
24
-
25
- <aside class="sidebar">
26
- <div>
27
- <div class="side-label">Pending changes</div>
28
- <div class="side-count" id="sideCount">0 changed</div>
29
- </div>
30
- <pre class="diff" id="diff"><span class="empty">// no changes yet</span></pre>
31
- <div class="btn-stack">
32
- <button class="btn primary" id="copyDiff" disabled>Copy diff</button>
33
- <button class="btn ghost" id="copyJson">Copy full JSON</button>
34
- <button class="btn ghost" id="reset">Reset</button>
35
- </div>
36
- </aside>
37
- </div>
38
-
39
- </div>
40
-
41
- <style>
42
- * { box-sizing: border-box; }
43
- .wrap {
44
- max-width: 1080px;
45
- margin: 0 auto;
46
- padding: 48px 32px 64px;
47
- }
48
- /* ---------- header ---------- */
49
- header { margin-bottom: 28px; }
50
- .eyebrow {
51
- font-family: var(--mono);
52
- font-size: 11px;
53
- letter-spacing: 0.08em;
54
- text-transform: uppercase;
55
- color: var(--gray-500);
56
- }
57
- h1 {
58
- font-family: var(--serif);
59
- font-weight: 500;
60
- font-size: 30px;
61
- letter-spacing: -0.01em;
62
- margin: 6px 0 4px;
63
- }
64
- .sub {
65
- color: var(--gray-500);
66
- max-width: 600px;
67
- }
68
- /* ---------- layout ---------- */
69
- .layout {
70
- display: grid;
71
- grid-template-columns: 1fr 320px;
72
- gap: 24px;
73
- align-items: start;
74
- }
75
- @media (max-width: 880px) {
76
- .layout { grid-template-columns: 1fr; }
77
- .sidebar { position: static !important; }
78
- }
79
- /* ---------- warning banner ---------- */
80
- .banner {
81
- grid-column: 1 / -1;
82
- display: none;
83
- align-items: baseline;
84
- gap: 8px;
85
- padding: 10px 14px;
86
- border: 1.5px solid #E8C2AE;
87
- background: #F8E9E0;
88
- color: var(--clay-d);
89
- border-radius: 10px;
90
- font-size: 13px;
91
- }
92
- .banner.show { display: flex; }
93
- .banner .glyph { font-size: 13px; line-height: 1; transform: translateY(1px); }
94
- /* ---------- groups / panels ---------- */
95
- .form-col { display: flex; flex-direction: column; gap: 16px; }
96
- .group {
97
- background: var(--white);
98
- border: 1.5px solid var(--gray-200);
99
- border-radius: 12px;
100
- overflow: hidden;
101
- }
102
- .group-head {
103
- font-family: var(--mono);
104
- font-size: 11px;
105
- letter-spacing: 0.08em;
106
- text-transform: uppercase;
107
- color: var(--gray-500);
108
- padding: 12px 18px 11px;
109
- border-bottom: 1.5px solid var(--gray-50);
110
- display: flex;
111
- justify-content: space-between;
112
- gap: 12px;
113
- }
114
- .group-head .name { color: var(--gray-800); }
115
- .group-head .meta { letter-spacing: 0.04em; }
116
- /* ---------- flag rows ---------- */
117
- .flag {
118
- display: flex;
119
- align-items: flex-start;
120
- gap: 16px;
121
- padding: 14px 18px;
122
- border-bottom: 1px solid var(--gray-50);
123
- border-left: 3px solid transparent;
124
- transition: border-left-color 140ms ease, background 140ms ease;
125
- }
126
- .flag:last-child { border-bottom: none; }
127
- .flag.warn {
128
- border-left-color: var(--clay);
129
- background: #FBF3EE;
130
- }
131
- .flag-info { flex: 1; min-width: 0; }
132
- .flag-key {
133
- font-family: var(--mono);
134
- font-size: 13px;
135
- color: var(--gray-800);
136
- display: flex;
137
- align-items: center;
138
- gap: 7px;
139
- flex-wrap: wrap;
140
- }
141
- .dot {
142
- display: none;
143
- width: 6px;
144
- height: 6px;
145
- border-radius: 50%;
146
- background: var(--clay);
147
- flex: none;
148
- }
149
- .flag.changed .dot { display: inline-block; }
150
- .rollout {
151
- font-family: var(--mono);
152
- font-size: 10px;
153
- letter-spacing: 0.03em;
154
- color: var(--gray-500);
155
- background: var(--gray-50);
156
- border: 1px solid var(--gray-200);
157
- border-radius: 999px;
158
- padding: 1px 7px 2px;
159
- }
160
- .flag-desc {
161
- color: var(--gray-500);
162
- font-size: 12.5px;
163
- margin-top: 2px;
164
- }
165
- .req-chip {
166
- display: inline-flex;
167
- align-items: baseline;
168
- gap: 5px;
169
- margin-top: 7px;
170
- font-family: var(--mono);
171
- font-size: 11px;
172
- color: var(--gray-500);
173
- background: var(--gray-50);
174
- border: 1px solid var(--gray-200);
175
- border-radius: 999px;
176
- padding: 2px 9px 3px;
177
- }
178
- .req-chip .warn-glyph { display: none; }
179
- .flag.warn .req-chip {
180
- color: var(--clay-d);
181
- background: #F8E1D5;
182
- border-color: #E8C2AE;
183
- }
184
- .flag.warn .req-chip .warn-glyph { display: inline; }
185
- /* ---------- toggle switch ---------- */
186
- .toggle {
187
- position: relative;
188
- flex: none;
189
- width: 38px;
190
- height: 22px;
191
- margin-top: 1px;
192
- }
193
- .toggle input {
194
- position: absolute;
195
- inset: 0;
196
- width: 100%;
197
- height: 100%;
198
- margin: 0;
199
- opacity: 0;
200
- cursor: pointer;
201
- }
202
- .toggle .track {
203
- position: absolute;
204
- inset: 0;
205
- background: var(--gray-200);
206
- border-radius: 999px;
207
- transition: background 160ms ease;
208
- pointer-events: none;
209
- }
210
- .toggle .track::after {
211
- content: "";
212
- position: absolute;
213
- top: 3px;
214
- left: 3px;
215
- width: 16px;
216
- height: 16px;
217
- border-radius: 50%;
218
- background: var(--white);
219
- box-shadow: 0 1px 2px rgba(20,20,19,0.18);
220
- transition: transform 160ms ease;
221
- }
222
- .toggle input:checked + .track { background: var(--olive); }
223
- .toggle input:checked + .track::after { transform: translateX(16px); }
224
- .toggle input:focus-visible + .track {
225
- outline: 2px solid var(--slate);
226
- outline-offset: 2px;
227
- }
228
- /* ---------- sidebar ---------- */
229
- .sidebar {
230
- position: sticky;
231
- top: 24px;
232
- background: var(--white);
233
- border: 1.5px solid var(--gray-200);
234
- border-radius: 12px;
235
- padding: 18px;
236
- display: flex;
237
- flex-direction: column;
238
- gap: 14px;
239
- }
240
- .side-label {
241
- font-family: var(--mono);
242
- font-size: 11px;
243
- letter-spacing: 0.08em;
244
- text-transform: uppercase;
245
- color: var(--gray-500);
246
- }
247
- .side-count {
248
- font-family: var(--serif);
249
- font-weight: 500;
250
- font-size: 19px;
251
- letter-spacing: -0.01em;
252
- }
253
- .side-count .warn-n { color: var(--clay-d); }
254
- .side-count .sep { color: var(--gray-200); margin: 0 4px; }
255
- .diff {
256
- margin: 0;
257
- font-family: var(--mono);
258
- font-size: 12px;
259
- line-height: 1.7;
260
- background: var(--gray-50);
261
- border: 1px solid var(--gray-200);
262
- border-radius: 8px;
263
- padding: 12px 14px;
264
- overflow-x: auto;
265
- max-height: 320px;
266
- overflow-y: auto;
267
- white-space: pre;
268
- }
269
- .diff .minus { color: var(--gray-500); }
270
- .diff .plus { color: var(--olive); }
271
- .diff .empty { color: var(--gray-500); }
272
- .btn {
273
- display: block;
274
- width: 100%;
275
- border: none;
276
- border-radius: 999px;
277
- font-family: var(--mono);
278
- font-size: 12px;
279
- padding: 9px 16px;
280
- cursor: pointer;
281
- text-align: center;
282
- transition: background 140ms ease, color 140ms ease, border-color 140ms ease;
283
- }
284
- .btn.primary {
285
- background: var(--slate);
286
- color: var(--ivory);
287
- }
288
- .btn.primary:hover:not(:disabled) { background: var(--gray-800); }
289
- .btn.primary:disabled {
290
- background: var(--gray-200);
291
- color: var(--gray-500);
292
- cursor: not-allowed;
293
- }
294
- .btn.primary.copied { background: var(--olive); }
295
- .btn.ghost {
296
- background: transparent;
297
- color: var(--gray-800);
298
- border: 1.5px solid var(--gray-200);
299
- }
300
- .btn.ghost:hover { border-color: var(--gray-500); }
301
- .btn.ghost.copied { border-color: var(--olive); color: var(--olive); }
302
- .btn-stack { display: flex; flex-direction: column; gap: 8px; }
303
- </style>
304
-
305
- <script>
306
- "use strict";
307
-
308
- // ---------------------------------------------------------------------------
309
- // data — the production flag set, as it exists right now
310
- // ---------------------------------------------------------------------------
311
-
312
- const GROUPS = [
313
- {
314
- id: "onboarding",
315
- label: "Onboarding",
316
- flags: [
317
- { key: "onboarding.checklist_v2",
318
- desc: "New three-step setup checklist replacing the modal tour.",
319
- on: true },
320
- { key: "onboarding.invite_nudge",
321
- desc: "Nudge owners to invite teammates after creating their first project.",
322
- on: true },
323
- { key: "onboarding.workspace_templates",
324
- desc: "Offer prebuilt workspace templates during signup.",
325
- on: false, requires: "onboarding.checklist_v2" },
326
- { key: "onboarding.skip_email_verify",
327
- desc: "Let SSO-domain users in before email verification completes.",
328
- on: false }
329
- ]
330
- },
331
- {
332
- id: "sync",
333
- label: "Sync engine",
334
- flags: [
335
- { key: "sync.delta_compression",
336
- desc: "Send field-level deltas instead of full document snapshots.",
337
- on: true },
338
- { key: "sync.offline_queue_v2",
339
- desc: "Persist offline edits to IndexedDB and replay on reconnect.",
340
- on: false, requires: "sync.delta_compression" },
341
- { key: "sync.presence_cursors",
342
- desc: "Show live collaborator cursors in board and doc views.",
343
- on: true },
344
- { key: "sync.conflict_banner",
345
- desc: "Surface a merge banner instead of silently last-write-wins.",
346
- on: false, requires: "sync.offline_queue_v2" },
347
- { key: "sync.binary_ws_frames",
348
- desc: "Switch the realtime channel to binary WebSocket frames.",
349
- on: false, rollout: 10 }
350
- ]
351
- },
352
- {
353
- id: "billing",
354
- label: "Billing",
355
- flags: [
356
- { key: "billing.usage_meter",
357
- desc: "Show per-seat usage meter on the workspace billing page.",
358
- on: false },
359
- { key: "billing.annual_discount_banner",
360
- desc: "Promote the annual-plan discount in the upgrade flow.",
361
- on: true },
362
- { key: "billing.proration_preview",
363
- desc: "Preview the prorated charge before confirming a plan change.",
364
- on: false, requires: "billing.usage_meter" },
365
- { key: "billing.dunning_emails_v3",
366
- desc: "Use the rewritten dunning sequence with a 14-day grace window.",
367
- on: true, rollout: 25 }
368
- ]
369
- },
370
- {
371
- id: "internal",
372
- label: "Internal",
373
- flags: [
374
- { key: "internal.shadow_traffic",
375
- desc: "Mirror 1% of API traffic to the staging cluster for diffing.",
376
- on: false },
377
- { key: "internal.query_tracing",
378
- desc: "Attach OpenTelemetry spans to every Postgres query.",
379
- on: true },
380
- { key: "internal.kill_switch_ui",
381
- desc: "Expose the emergency kill-switch panel in the admin console.",
382
- on: true, requires: "internal.query_tracing" }
383
- ]
384
- }
385
- ];
386
-
387
- // flat lookup, plus a frozen copy of initial state
388
- const FLAGS = {};
389
- const INITIAL = {};
390
- GROUPS.forEach(g => g.flags.forEach(f => {
391
- FLAGS[f.key] = f;
392
- INITIAL[f.key] = f.on;
393
- }));
394
-
395
- // ---------------------------------------------------------------------------
396
- // render the form once; state lives in the checkboxes
397
- // ---------------------------------------------------------------------------
398
-
399
- const formCol = document.getElementById("formCol");
400
- const banner = document.getElementById("banner");
401
- const bannerTxt = document.getElementById("bannerText");
402
- const sideCount = document.getElementById("sideCount");
403
- const diffEl = document.getElementById("diff");
404
- const copyDiffBtn = document.getElementById("copyDiff");
405
- const copyJsonBtn = document.getElementById("copyJson");
406
- const resetBtn = document.getElementById("reset");
407
-
408
- function esc(s) {
409
- return String(s).replace(/[&<>"]/g, c =>
410
- ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));
411
- }
412
-
413
- function buildForm() {
414
- formCol.innerHTML = "";
415
- for (const g of GROUPS) {
416
- const panel = document.createElement("section");
417
- panel.className = "group";
418
- panel.dataset.group = g.id;
419
-
420
- const head = document.createElement("div");
421
- head.className = "group-head";
422
- head.innerHTML =
423
- '<span class="name">' + esc(g.label) + '</span>' +
424
- '<span class="meta" data-meta></span>';
425
- panel.appendChild(head);
426
-
427
- for (const f of g.flags) {
428
- const row = document.createElement("div");
429
- row.className = "flag";
430
- row.dataset.key = f.key;
431
-
432
- const rolloutBadge = (typeof f.rollout === "number")
433
- ? '<span class="rollout">' + f.rollout + '%</span>'
434
- : "";
435
-
436
- const reqChip = f.requires
437
- ? '<div class="req-chip"><span class="warn-glyph">&#9888;</span>' +
438
- '<span>requires ' + esc(f.requires) + '</span></div>'
439
- : "";
440
-
441
- row.innerHTML =
442
- '<label class="toggle">' +
443
- '<input type="checkbox" data-key="' + esc(f.key) + '"' +
444
- (f.on ? " checked" : "") +
445
- ' aria-label="' + esc(f.key) + '">' +
446
- '<span class="track"></span>' +
447
- '</label>' +
448
- '<div class="flag-info">' +
449
- '<div class="flag-key"><span class="dot"></span>' +
450
- '<span>' + esc(f.key) + '</span>' + rolloutBadge +
451
- '</div>' +
452
- '<div class="flag-desc">' + esc(f.desc) + '</div>' +
453
- reqChip +
454
- '</div>';
455
-
456
- panel.appendChild(row);
457
- }
458
- formCol.appendChild(panel);
459
- }
460
- }
461
-
462
- // ---------------------------------------------------------------------------
463
- // state helpers
464
- // ---------------------------------------------------------------------------
465
-
466
- function currentState() {
467
- const state = {};
468
- formCol.querySelectorAll("input[type=checkbox]").forEach(cb => {
469
- state[cb.dataset.key] = cb.checked;
470
- });
471
- return state;
472
- }
473
-
474
- function computeDiff(state) {
475
- const lines = [];
476
- for (const g of GROUPS) {
477
- for (const f of g.flags) {
478
- if (state[f.key] !== INITIAL[f.key]) {
479
- lines.push({ key: f.key, from: INITIAL[f.key], to: state[f.key] });
480
- }
481
- }
482
- }
483
- return lines;
484
- }
485
-
486
- function diffText(lines) {
487
- return lines.map(l =>
488
- '- "' + l.key + '": ' + l.from + '\n' +
489
- '+ "' + l.key + '": ' + l.to
490
- ).join("\n");
491
- }
492
-
493
- function fullJson(state) {
494
- // grouped, ordered, pretty-printed — what you'd actually paste into the file
495
- let out = "{\n";
496
- GROUPS.forEach((g, gi) => {
497
- g.flags.forEach((f, fi) => {
498
- const last = gi === GROUPS.length - 1 && fi === g.flags.length - 1;
499
- out += ' "' + f.key + '": ' + state[f.key] + (last ? "" : ",") + "\n";
500
- });
501
- });
502
- out += "}";
503
- return out;
504
- }
505
-
506
- // ---------------------------------------------------------------------------
507
- // the live update pass
508
- // ---------------------------------------------------------------------------
509
-
510
- function update() {
511
- const state = currentState();
512
- let warnCount = 0;
513
-
514
- // per-row decorations
515
- for (const g of GROUPS) {
516
- let onCount = 0;
517
- for (const f of g.flags) {
518
- const row = formCol.querySelector('.flag[data-key="' + CSS.escape(f.key) + '"]');
519
- const on = state[f.key];
520
- if (on) onCount++;
521
-
522
- const changed = on !== INITIAL[f.key];
523
- row.classList.toggle("changed", changed);
524
-
525
- let warn = false;
526
- if (f.requires && on && !state[f.requires]) warn = true;
527
- row.classList.toggle("warn", warn);
528
- if (warn) warnCount++;
529
- }
530
- const meta = formCol.querySelector(
531
- '.group[data-group="' + g.id + '"] [data-meta]');
532
- meta.textContent = g.flags.length + " flags, " + onCount + " on";
533
- }
534
-
535
- // diff + sidebar
536
- const lines = computeDiff(state);
537
- if (lines.length === 0) {
538
- diffEl.innerHTML = '<span class="empty">// no changes yet</span>';
539
- copyDiffBtn.disabled = true;
540
- } else {
541
- diffEl.innerHTML = lines.map(l =>
542
- '<span class="minus">- "' + esc(l.key) + '": ' + l.from + '</span>\n' +
543
- '<span class="plus">+ "' + esc(l.key) + '": ' + l.to + '</span>'
544
- ).join("\n");
545
- copyDiffBtn.disabled = false;
546
- }
547
-
548
- sideCount.innerHTML =
549
- lines.length + " changed" +
550
- '<span class="sep">&middot;</span>' +
551
- '<span class="' + (warnCount ? "warn-n" : "") + '">' +
552
- warnCount + " warning" + (warnCount === 1 ? "" : "s") +
553
- "</span>";
554
-
555
- // banner
556
- if (warnCount > 0) {
557
- bannerTxt.textContent =
558
- warnCount === 1
559
- ? "1 flag is enabled without its prerequisite"
560
- : warnCount + " flags are enabled without their prerequisites";
561
- banner.classList.add("show");
562
- } else {
563
- banner.classList.remove("show");
564
- }
565
- }
566
-
567
- // ---------------------------------------------------------------------------
568
- // clipboard + buttons
569
- // ---------------------------------------------------------------------------
570
-
571
- function flash(btn, label) {
572
- const orig = btn.textContent;
573
- btn.textContent = label;
574
- btn.classList.add("copied");
575
- btn.disabled = true;
576
- setTimeout(() => {
577
- btn.textContent = orig;
578
- btn.classList.remove("copied");
579
- btn.disabled = false;
580
- update(); // restore disabled state on copyDiff if needed
581
- }, 1200);
582
- }
583
-
584
- function writeClipboard(text) {
585
- if (navigator.clipboard && navigator.clipboard.writeText) {
586
- return navigator.clipboard.writeText(text);
587
- }
588
- // fallback for file:// contexts without clipboard permission
589
- const ta = document.createElement("textarea");
590
- ta.value = text;
591
- ta.style.position = "fixed";
592
- ta.style.left = "-9999px";
593
- document.body.appendChild(ta);
594
- ta.select();
595
- try { document.execCommand("copy"); } catch (e) { /* ignore */ }
596
- document.body.removeChild(ta);
597
- return Promise.resolve();
598
- }
599
-
600
- copyDiffBtn.addEventListener("click", () => {
601
- const lines = computeDiff(currentState());
602
- if (!lines.length) return;
603
- writeClipboard(diffText(lines)).then(() => flash(copyDiffBtn, "Copied ✓"));
604
- });
605
-
606
- copyJsonBtn.addEventListener("click", () => {
607
- writeClipboard(fullJson(currentState())).then(() => flash(copyJsonBtn, "Copied ✓"));
608
- });
609
-
610
- resetBtn.addEventListener("click", () => {
611
- formCol.querySelectorAll("input[type=checkbox]").forEach(cb => {
612
- cb.checked = INITIAL[cb.dataset.key];
613
- });
614
- update();
615
- });
616
-
617
- formCol.addEventListener("change", e => {
618
- if (e.target.matches("input[type=checkbox]")) update();
619
- });
620
-
621
- // ---------------------------------------------------------------------------
622
- // boot
623
- // ---------------------------------------------------------------------------
624
-
625
- buildForm();
626
- update();
627
- </script>