wicked-interactive 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ :root{--wi-accent: #4f46e5;--wi-accent-h: #4338ca;--wi-accent-2: #7c3aed;--wi-accent-soft: #eef2ff;--wi-ring: rgba(79, 70, 229, .35);--wi-pending: #f59e0b;--wi-bg: #ffffff;--wi-panel: #ffffff;--wi-surface: #fbfbfd;--wi-surface-2: #e8eaef;--wi-field: #f4f5f8;--wi-line: rgba(15, 23, 42, .08);--wi-line-2: rgba(15, 23, 42, .12);--wi-ink: #0f172a;--wi-ink-soft: #6b7280;--wi-ink-faint: #9aa3b2;--wi-text-xs: 11px;--wi-text-sm: 12.5px;--wi-text-base: 13.5px;--wi-text-md: 15px;--wi-text-lg: 19px;--wi-text-xl: 23px;--wi-lh-tight: 1.25;--wi-lh-base: 1.5;--wi-r-sm: 6px;--wi-r-md: 10px;--wi-r-lg: 14px;--wi-sh-1: 0 1px 2px rgba(15,23,42,.04), 0 1px 1px rgba(15,23,42,.04);--wi-sh-2: 0 2px 4px rgba(15,23,42,.04), 0 8px 20px rgba(15,23,42,.06);--wi-sh-3: 0 12px 28px rgba(15,23,42,.12), 0 32px 64px rgba(15,23,42,.16);--wi-ease: cubic-bezier(.2, .8, .2, 1);--wi-canvas-bg: hsl(220, 16%, 91%);--wi-chrome: hsl(224, 30%, 14%);--wi-chrome-2: hsl(224, 24%, 21%);--wi-chrome-3: hsl(224, 22%, 27%);--wi-chrome-line: rgba(255,255,255,.06);--wi-chrome-line-2: rgba(255,255,255,.12);--wi-chrome-ink: hsl(220, 24%, 92%);--wi-chrome-ink-soft: hsl(220, 16%, 68%);--wi-chrome-ink-faint: hsl(220, 12%, 50%);--wi-accent-bright: hsl(245, 100%, 85%);--wi-panel-bg: hsl(220, 20%, 98%);--wi-panel-2: hsl(220, 16%, 95%);--wi-sep-down: 0 1px 0 rgba(255,255,255,.05), 0 8px 28px rgba(20,24,40,.45);--wi-sep-right: -1px 0 0 rgba(15,23,42,.04), -14px 0 32px rgba(15,23,42,.1);--wi-doc-shadow: 0 1px 3px rgba(15,23,42,.1), 0 8px 24px rgba(15,23,42,.12), 0 24px 56px rgba(15,23,42,.1)}*{box-sizing:border-box}body{margin:0;color:var(--wi-ink);background:var(--wi-bg);font-family:Inter,system-ui,-apple-system,Segoe UI,sans-serif;font-size:var(--wi-text-base);line-height:var(--wi-lh-base);font-feature-settings:"cv02","cv03","cv04","cv11";-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility}:where(button,input,textarea,select,[tabindex]):focus-visible{outline:none;border-color:var(--wi-accent);box-shadow:0 0 0 3px var(--wi-ring)}@media(prefers-reduced-motion:reduce){*{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}}.wi-shell{display:grid;grid-template-columns:56px 1fr 340px;grid-template-rows:46px 1fr;height:100vh;width:100vw;overflow:hidden;background:var(--wi-canvas-bg)}.wi-shell--chat-collapsed{grid-template-columns:56px 1fr 56px}.wi-toolbar{grid-column:1 / -1;grid-row:1;z-index:6;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:0 16px 0 14px;background:var(--wi-chrome);color:var(--wi-chrome-ink);box-shadow:var(--wi-sep-down)}.wi-toolbar__group{display:flex;align-items:center;gap:10px;min-width:0}.wi-toolbar__group--center{flex:1 1 auto;justify-content:center;overflow:hidden}.wi-logo{display:inline-flex;align-items:center;gap:9px;-webkit-user-select:none;user-select:none}.wi-logo__mark{width:26px;height:26px;border-radius:8px;flex:0 0 auto;display:grid;place-items:center;background:linear-gradient(135deg,var(--wi-accent-bright),var(--wi-accent-2));box-shadow:0 0 0 1px #ffffff24,0 3px 10px #7c3aed80}.wi-logo__mark svg{display:block}.wi-logo__word{font-size:var(--wi-text-md);font-weight:700;letter-spacing:-.02em;line-height:1;white-space:nowrap}.wi-logo__word b{font-weight:700;color:var(--wi-chrome-ink)}.wi-logo__word i{font-style:normal;font-weight:600;color:var(--wi-accent-bright)}.wi-toolbar__crumb{display:inline-flex;align-items:center;gap:10px;min-width:0}.wi-toolbar__crumb:before{content:"/";color:var(--wi-chrome-ink-faint);font-size:16px;font-weight:300}.wi-toolbar__docname{font-weight:600;font-size:var(--wi-text-base);letter-spacing:-.01em;color:var(--wi-chrome-ink-soft);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px}.wi-rail{grid-column:1;grid-row:2;position:relative;z-index:20}.wi-rail__inner{position:absolute;top:0;bottom:0;left:0;width:56px;display:flex;flex-direction:column;gap:6px;padding:12px 9px;background:var(--wi-chrome);color:var(--wi-chrome-ink-soft);box-shadow:inset -1px 0 #00000059;overflow-x:hidden;overflow-y:auto;transition:width .2s var(--wi-ease),box-shadow .2s var(--wi-ease)}.wi-rail:hover .wi-rail__inner{width:232px;box-shadow:inset -1px 0 #00000059,18px 0 44px #0f172a66}.wi-rail__row{display:flex;align-items:center;gap:12px;width:100%}.wi-rail__label{font-size:var(--wi-text-sm);font-weight:500;white-space:nowrap;opacity:0;transform:translate(-4px);pointer-events:none;transition:opacity .14s var(--wi-ease),transform .14s var(--wi-ease)}.wi-rail:hover .wi-rail__label{opacity:1;transform:none}.wi-rail__new-row{cursor:pointer;border:0;background:transparent;padding:0;margin:0;border-radius:var(--wi-r-md);transition:background .14s var(--wi-ease)}.wi-rail__new-row:hover{background:#ffffff0a}.wi-rail__new{width:34px;height:34px;border-radius:9px;flex:0 0 auto;display:grid;place-items:center;line-height:0;color:#fff;background:var(--wi-accent);box-shadow:0 2px 8px #4f46e573;transition:background .14s var(--wi-ease),transform .08s var(--wi-ease)}.wi-rail__new-row:hover .wi-rail__new{background:var(--wi-accent-h)}.wi-rail__new-row:active .wi-rail__new{transform:scale(.92)}.wi-rail__new-label{color:var(--wi-chrome-ink);font-weight:600}.wi-rail__sep{height:1px;margin:9px 6px;background:var(--wi-chrome-line);flex:0 0 auto}.wi-rail__docs{display:flex;flex-direction:column;gap:2px;overflow-y:auto;overflow-x:hidden}.wi-rail__doc{position:relative;display:flex;align-items:center;gap:11px;width:100%;cursor:pointer;border:0;text-align:left;padding:3px;border-radius:var(--wi-r-md);background:transparent;color:var(--wi-chrome-ink-soft);transition:background .14s var(--wi-ease),color .14s var(--wi-ease)}.wi-rail__doc:before{content:"";position:absolute;left:-9px;top:50%;transform:translateY(-50%) scaleY(0);width:3px;height:20px;border-radius:0 3px 3px 0;background:var(--wi-accent-bright);transition:transform .16s var(--wi-ease)}.wi-rail__doc-glyph{width:34px;height:34px;border-radius:9px;flex:0 0 auto;display:flex;align-items:center;justify-content:center;font-size:var(--wi-text-sm);font-weight:600;text-transform:uppercase;color:var(--wi-chrome-ink-soft);background:var(--wi-chrome-2);transition:background .14s var(--wi-ease),color .14s var(--wi-ease)}.wi-rail__doc:hover{color:var(--wi-chrome-ink)}.wi-rail__doc:hover .wi-rail__doc-glyph{background:var(--wi-chrome-3);color:var(--wi-chrome-ink)}.wi-rail__doc.is-active{color:#fff}.wi-rail__doc.is-active:before{transform:translateY(-50%) scaleY(1)}.wi-rail__doc.is-active .wi-rail__doc-glyph{background:var(--wi-accent);color:#fff}.wi-rail__section{display:flex;flex-direction:column;gap:4px}.wi-rail__heading{padding:2px 3px 0}.wi-rail__heading .wi-rail__label{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--wi-chrome-ink-soft);opacity:0}.wi-rail:hover .wi-rail__heading .wi-rail__label{opacity:.6}.wi-rail__empty{padding:2px 3px 4px 45px}.wi-rail__empty .wi-rail__label{font-size:var(--wi-text-sm);color:var(--wi-chrome-ink-soft);opacity:.45;font-style:italic}.wi-demo{max-width:860px;margin:0 auto;padding:8px 4px 40px}.wi-demo__head{margin-bottom:18px}.wi-demo__target{color:var(--wi-ink-soft);font-size:var(--wi-text-sm);margin-top:4px}.wi-demo__target a{color:var(--wi-accent);text-decoration:none}.wi-demo__target a:hover{text-decoration:underline}.wi-demo__player{border-radius:var(--wi-r-md);overflow:hidden;background:#0b1020;box-shadow:var(--wi-sh-2, 0 8px 30px rgba(15,23,42,.18));margin-bottom:22px}.wi-demo__player video{display:block;width:100%;height:auto;background:#0b1020}.wi-demo__steps{list-style:none;counter-reset:wi-step;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}.wi-demo__step{counter-increment:wi-step;position:relative;display:flex;align-items:baseline;gap:12px;padding:11px 14px 11px 46px;border:1px solid var(--wi-line-2);border-radius:var(--wi-r-sm);background:#fff}.wi-demo__step:before{content:counter(wi-step);position:absolute;left:12px;top:10px;width:22px;height:22px;border-radius:50%;display:grid;place-items:center;font-size:12px;font-weight:700;color:#fff;background:var(--wi-accent)}.wi-demo__step-label{font-weight:500;color:var(--wi-ink)}.wi-demo__at{margin-left:auto;font-size:12px;color:var(--wi-ink-faint);font-variant-numeric:tabular-nums}.wi-demo__nosteps{color:var(--wi-ink-soft);font-style:italic}.wi-btn{border:1px solid var(--wi-line-2);background:#fff;color:var(--wi-ink);padding:7px 13px;border-radius:var(--wi-r-sm);cursor:pointer;font-size:13px;font-weight:500;box-shadow:var(--wi-sh-1);transition:background .15s var(--wi-ease),border-color .15s var(--wi-ease),box-shadow .15s var(--wi-ease),transform .06s var(--wi-ease)}.wi-btn:hover{background:var(--wi-field);border-color:var(--wi-ink-faint)}.wi-btn:active{transform:translateY(1px)}.wi-btn:disabled{opacity:.45;cursor:not-allowed;box-shadow:none}.wi-btn--ghost{background:transparent;color:var(--wi-ink-soft);border-color:transparent;box-shadow:none}.wi-btn--ghost:hover{background:var(--wi-field);color:var(--wi-ink);border-color:transparent}.wi-btn--primary{background:var(--wi-accent);color:#fff;border-color:transparent;box-shadow:0 1px 2px #4f46e54d,0 4px 12px #4f46e547}.wi-btn--primary:hover{background:var(--wi-accent-h);border-color:transparent}.wi-btn--primary:disabled{opacity:.45;cursor:not-allowed;box-shadow:none}.wi-toolbar .wi-btn{background:var(--wi-chrome-2);border-color:transparent;color:var(--wi-chrome-ink);box-shadow:inset 0 1px #ffffff14,0 1px 2px #00000059}.wi-toolbar .wi-btn:hover{background:var(--wi-chrome-3);box-shadow:inset 0 1px #ffffff1a,0 2px 6px #0006}.wi-toolbar .wi-btn--ghost{background:transparent;border-color:transparent;color:var(--wi-chrome-ink-soft);box-shadow:none}.wi-toolbar .wi-btn--ghost:hover{background:#ffffff14;color:var(--wi-chrome-ink);box-shadow:none}.wi-toolbar .wi-btn--primary{background:var(--wi-accent);color:#fff;border-color:transparent;box-shadow:inset 0 1px #ffffff38,0 2px 8px #4f46e580}.wi-toolbar .wi-btn--primary:hover{background:var(--wi-accent-h)}.wi-box__x{position:absolute;top:-11px;right:-11px;width:22px;height:22px;border-radius:50%;border:2px solid #fff;background:#ef4444;color:#fff;font-size:13px;line-height:1;cursor:pointer;pointer-events:auto;box-shadow:0 2px 8px #0000004d;display:flex;align-items:center;justify-content:center}.wi-box__x:hover{background:#dc2626;transform:scale(1.12)}.wi-status{display:inline-flex;align-items:center;gap:7px;max-width:320px;padding:5px 12px;border-radius:999px;font-size:var(--wi-text-sm);font-weight:500;background:#ffffff0f;color:var(--wi-chrome-ink-soft);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wi-status:before{content:"";width:7px;height:7px;border-radius:50%;flex:0 0 auto}.wi-status--ok{color:#a5e9c7;background:#2eb87324}.wi-status--ok:before{background:#42d78c}.wi-status--warn{color:#f9d494;background:#f29e0d24}.wi-status--warn:before{background:#f6ae31}.wi-status--error{color:#f5a3a3;background:#e8303029}.wi-status--error:before{background:#ef4d4d}.wi-canvas{grid-column:2;grid-row:2;position:relative;overflow:hidden;background:var(--wi-canvas-bg)}.wi-doc{position:absolute;top:22px;right:22px;bottom:22px;left:22px;min-width:0;border-radius:var(--wi-r-lg);overflow:visible;transition:box-shadow .4s var(--wi-ease)}.wi-doc iframe{width:100%;height:100%;border:0;background:#fff;border-radius:var(--wi-r-lg);box-shadow:var(--wi-doc-shadow);animation:wi-frame-in .26s var(--wi-ease) both}@keyframes wi-frame-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}.wi-doc--busy iframe{animation:wi-doc-breathe 2s ease-in-out infinite}@keyframes wi-doc-breathe{0%,to{box-shadow:var(--wi-doc-shadow)}50%{box-shadow:var(--wi-doc-shadow),0 0 0 3px #6a5eed59,0 0 32px 6px #5547eb47}}.wi-spacer{flex:1 1 auto}.wi-tip{position:fixed;bottom:18px;left:50%;transform:translate(-50%);background:#0f172aeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#e2e8f0;font-size:13px;font-weight:500;padding:9px 18px;border-radius:999px;box-shadow:var(--wi-sh-3)}.wi-inline{position:absolute;z-index:8;width:340px;max-width:90vw;background:#fff;border:1px solid var(--wi-line);border-radius:var(--wi-r-md);box-shadow:var(--wi-sh-3);padding:14px;animation:wi-modal-in .18s var(--wi-ease) both}.wi-inline textarea{width:100%;font:inherit;padding:9px 11px;border:1px solid var(--wi-line-2);border-radius:var(--wi-r-sm);resize:vertical;box-sizing:border-box}.wi-inline__scope{display:flex;gap:4px;margin-bottom:10px;background:var(--wi-field);padding:3px;border-radius:var(--wi-r-sm)}.wi-inline__scope button{flex:1;border:0;background:transparent;color:var(--wi-ink-soft);font:inherit;font-size:12px;font-weight:500;padding:6px;border-radius:7px;cursor:pointer;transition:background .15s var(--wi-ease),color .15s var(--wi-ease)}.wi-inline__scope button.on{color:var(--wi-accent);background:#fff;font-weight:600;box-shadow:var(--wi-sh-1)}.wi-inline__actions{display:flex;gap:8px;margin-top:10px}.wi-side{grid-column:3;grid-row:2;display:flex;flex-direction:column;min-height:0;z-index:4;background:var(--wi-panel-bg);color:var(--wi-ink);box-shadow:var(--wi-sep-right)}.wi-sources{flex:0 0 auto;display:flex;flex-direction:column;min-height:0;max-height:42%;border-bottom:1px solid var(--wi-line)}.wi-sources__head{padding:0 16px;height:46px;flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;font-weight:600;font-size:var(--wi-text-sm);letter-spacing:.04em;text-transform:uppercase;color:var(--wi-ink-soft)}.wi-sources__add{border:1px dashed var(--wi-line);background:none;cursor:pointer;color:var(--wi-ink-soft);font-size:var(--wi-text-sm);padding:4px 9px;border-radius:var(--wi-r-sm);text-transform:none;letter-spacing:0;font-weight:500;transition:border-color .15s var(--wi-ease),background .15s var(--wi-ease),color .15s var(--wi-ease)}.wi-sources__add:hover{border-color:var(--wi-accent);background:var(--wi-accent-soft);color:var(--wi-accent)}.wi-sources__list{flex:1 1 auto;overflow-y:auto;padding:6px 16px 14px;display:flex;flex-direction:column;gap:8px}.wi-sources__empty{color:var(--wi-ink-faint);font-size:var(--wi-text-sm);line-height:var(--wi-lh-base);margin:2px 0 0}.wi-source{display:flex;flex-direction:column;gap:2px;padding:8px 10px;border:1px solid var(--wi-line);border-radius:var(--wi-r-md);background:var(--wi-panel-2)}.wi-source__name{font-weight:600;font-size:var(--wi-text-sm);color:var(--wi-ink)}.wi-source__path{font-size:11px;color:var(--wi-ink-faint);font-family:var(--wi-mono, ui-monospace, monospace);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:rtl;text-align:left}.wi-source__note{font-size:var(--wi-text-sm);color:var(--wi-ink-soft);font-style:italic}.wi-source__status{align-self:flex-start;margin-top:3px;font-size:11px;font-weight:600;padding:1px 7px;border-radius:999px}.wi-source__status--pending{background:#f1f5f9;color:#64748b}.wi-source__status--indexing{background:#fef9c3;color:#854d0e}.wi-source__status--indexed{background:#dcfce7;color:#166534}.wi-source__status--error{background:#fee2e2;color:#991b1b}.wi-sources--collapsed{max-height:none;align-items:center;padding-top:12px;border-bottom:0}.wi-sources__toggle{position:relative;border:0;background:none;cursor:pointer;font-size:20px;padding:6px;border-radius:var(--wi-r-sm)}.wi-sources__toggle:hover{background:var(--wi-panel-2)}.wi-sources__badge{position:absolute;top:-2px;right:-2px;min-width:16px;height:16px;padding:0 4px;border-radius:999px;background:var(--wi-accent);color:#fff;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center}.wi-fspicker{max-width:620px;display:flex;flex-direction:column}.wi-fspicker__bar{display:flex;align-items:center;gap:8px;margin-bottom:8px}.wi-fspicker__nav{flex:0 0 auto;width:32px;height:32px;border-radius:var(--wi-r-sm);border:1px solid var(--wi-line);background:#fff;cursor:pointer;font-size:15px;color:var(--wi-ink-soft)}.wi-fspicker__nav:hover:not(:disabled){border-color:var(--wi-accent);color:var(--wi-accent)}.wi-fspicker__nav:disabled{opacity:.4;cursor:default}.wi-fspicker__crumbs{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:1px;overflow-x:auto;white-space:nowrap;padding:4px 0}.wi-fspicker__crumb{border:0;background:none;cursor:pointer;color:var(--wi-ink-soft);font-size:var(--wi-text-sm);padding:2px 5px;border-radius:var(--wi-r-sm)}.wi-fspicker__crumb:hover{background:var(--wi-accent-soft);color:var(--wi-accent)}.wi-fspicker__crumb:after{content:"/";color:var(--wi-ink-faint);margin-left:3px}.wi-fspicker__crumb:last-child:after{content:""}.wi-fspicker__list{height:300px;overflow-y:auto;border:1px solid var(--wi-line);border-radius:var(--wi-r-md);padding:4px}.wi-fspicker__empty{color:var(--wi-ink-faint);font-size:var(--wi-text-sm);padding:16px;text-align:center}.wi-fspicker__row{display:flex;align-items:center;gap:8px;border-radius:var(--wi-r-sm)}.wi-fspicker__row.is-picked{background:var(--wi-accent-soft)}.wi-fspicker__row:hover{background:var(--wi-panel-2)}.wi-fspicker__check{flex:0 0 auto;padding:0 4px 0 8px;display:flex;align-items:center}.wi-fspicker__name{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:8px;border:0;background:none;cursor:pointer;text-align:left;padding:7px 8px;font:inherit;color:var(--wi-ink)}.wi-fspicker__name:disabled{cursor:default}.wi-fspicker__glyph{flex:0 0 auto}.wi-fspicker__label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wi-fspicker__chev{flex:0 0 auto;color:var(--wi-ink-faint)}.wi-fspicker__picked{display:flex;flex-wrap:wrap;gap:6px;margin-top:10px}.wi-fspicker__chip{display:inline-flex;align-items:center;gap:5px;padding:3px 6px 3px 9px;border-radius:999px;background:var(--wi-accent-soft);color:var(--wi-accent);font-size:var(--wi-text-sm);font-weight:500}.wi-fspicker__chip button{border:0;background:none;cursor:pointer;color:inherit;font-size:15px;line-height:1;padding:0 2px}.wi-chat{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;background:var(--wi-panel-bg);color:var(--wi-ink)}.wi-chat__head{padding:0 16px;height:46px;flex:0 0 auto;font-weight:600;font-size:var(--wi-text-sm);letter-spacing:.04em;text-transform:uppercase;color:var(--wi-ink-soft);display:flex;align-items:center;justify-content:space-between}.wi-chat__toggle{border:0;background:none;cursor:pointer;font-size:16px;color:var(--wi-ink-faint);padding:4px 7px;border-radius:var(--wi-r-sm);transition:background .15s var(--wi-ease),color .15s var(--wi-ease)}.wi-chat__toggle:hover{background:var(--wi-panel-2);color:var(--wi-ink)}.wi-chat--collapsed{align-items:center;padding-top:12px}.wi-chat--collapsed .wi-chat__head{justify-content:center;padding:0}.wi-chat--collapsed .wi-chat__toggle{font-size:22px}.wi-chat__log{flex:1 1 auto;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}.wi-chat__empty{margin:auto;max-width:240px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:6px}.wi-chat__empty-icon{width:48px;height:48px;border-radius:14px;display:flex;align-items:center;justify-content:center;background:var(--wi-accent-soft);color:var(--wi-accent);margin-bottom:4px;box-shadow:var(--wi-sh-1)}.wi-chat__empty-title{margin:0;font-size:var(--wi-text-md);font-weight:600;color:var(--wi-ink);letter-spacing:-.01em}.wi-chat__hint{color:var(--wi-ink-faint);font-size:var(--wi-text-sm);line-height:var(--wi-lh-base);margin:0}.wi-chat__hint em{color:var(--wi-ink-soft);font-style:italic}.wi-msg{font-size:var(--wi-text-base);line-height:var(--wi-lh-base);display:flex;flex-direction:column;gap:3px;max-width:100%;animation:wi-msg-in .18s var(--wi-ease) both}@keyframes wi-msg-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}.wi-msg__who{font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;font-weight:600;color:var(--wi-ink-faint)}.wi-msg__text{padding:9px 12px;border-radius:14px;white-space:pre-wrap;word-break:break-word}.wi-msg--user{align-items:flex-end}.wi-msg--user .wi-msg__text{background:var(--wi-accent);color:#fff;border-bottom-right-radius:4px;box-shadow:0 2px 8px #4f46e54d}.wi-msg--agent .wi-msg__text{background:var(--wi-panel-2);color:var(--wi-ink);border-bottom-left-radius:4px}.wi-msg--event{align-items:center}.wi-msg__event{font-size:12px;color:var(--wi-accent);background:var(--wi-accent-soft);padding:3px 11px;border-radius:999px}.wi-chat__input{display:flex;gap:8px;padding:14px}.wi-chat__input textarea{flex:1;font:inherit;padding:9px 11px;border:1px solid var(--wi-line);border-radius:var(--wi-r-md);resize:none;background:#fff;color:var(--wi-ink);box-shadow:inset 0 1px 2px #0f172a0d;transition:border-color .15s var(--wi-ease),box-shadow .15s var(--wi-ease)}.wi-chat__input textarea::placeholder{color:var(--wi-ink-faint)}.wi-chat__input textarea:hover{border-color:var(--wi-line-2)}.wi-chat__input textarea:focus{border-color:var(--wi-accent);box-shadow:0 0 0 3px var(--wi-ring)}.wi-overlay{position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none}.wi-box{position:absolute;border:2px solid transparent;border-radius:3px}.wi-box--hover{border-color:var(--wi-accent);background:#2563eb14}.wi-box--selected{border-color:var(--wi-accent);box-shadow:0 0 0 2px #2563eb4d}.wi-box--pending{border-color:var(--wi-pending);border-style:dashed}.wi-box__tag{position:absolute;top:-18px;left:0;font-size:10px;background:var(--wi-pending);color:#fff;padding:1px 5px;border-radius:3px}.wi-side{flex:0 0 340px;border-left:1px solid var(--wi-line);padding:18px;overflow-y:auto;background:var(--wi-surface)}.wi-hint{color:var(--wi-ink-soft)}.wi-panel{display:flex;flex-direction:column;gap:11px;background:var(--wi-panel);border:1px solid var(--wi-line);border-radius:var(--wi-r-md);padding:14px;box-shadow:var(--wi-sh-2)}.wi-panel__head code{background:var(--wi-accent-soft);color:var(--wi-accent);padding:2px 6px;border-radius:5px;font-size:12px}.wi-panel__before{color:var(--wi-ink-soft);font-style:italic;font-size:13px}.wi-modes{display:flex;gap:3px;background:var(--wi-field);border-radius:var(--wi-r-sm);padding:3px}.wi-mode{flex:1;border:0;background:transparent;color:var(--wi-ink-soft);font:inherit;font-size:12px;font-weight:500;padding:7px 4px;border-radius:7px;cursor:pointer;transition:background .15s var(--wi-ease),color .15s var(--wi-ease)}.wi-mode--on{background:#fff;color:var(--wi-accent);font-weight:600;box-shadow:var(--wi-sh-1)}.wi-mode__hint{font-size:12px;color:var(--wi-ink-soft);margin:4px 2px 0}.wi-target{display:flex;gap:6px}.wi-target__b{flex:1;border:1px solid var(--wi-line-2);background:#fff;color:var(--wi-ink-soft);font:inherit;font-size:12px;font-weight:500;padding:6px;border-radius:7px;cursor:pointer;transition:border-color .15s var(--wi-ease),color .15s var(--wi-ease),background .15s var(--wi-ease)}.wi-target__b.on{border-color:var(--wi-accent);color:var(--wi-accent);font-weight:600;background:var(--wi-accent-soft)}.wi-color{display:flex;align-items:center;justify-content:space-between;font-size:13px;color:#334155}.wi-color input{width:48px;height:30px;padding:0;border:1px solid var(--wi-line-2);border-radius:7px;cursor:pointer}.wi-field{display:flex;flex-direction:column;gap:5px;font-size:13px;font-weight:500;color:#334155}.wi-field select,.wi-field textarea,.wi-field input{font:inherit;font-weight:400;padding:8px 10px;border:1px solid var(--wi-line-2);border-radius:var(--wi-r-sm);background:#fff;transition:border-color .15s var(--wi-ease)}.wi-field select:hover,.wi-field textarea:hover,.wi-field input:hover{border-color:var(--wi-ink-faint)}.wi-panel__actions{display:flex;gap:8px}.wi-pending{margin-top:20px}.wi-pending h3{font-size:13px;text-transform:uppercase;color:#64748b;letter-spacing:.4px}.wi-pending__item{display:flex;align-items:center;gap:6px;padding:6px 0;border-bottom:1px solid var(--wi-line);font-size:13px}.wi-pending__item code{font-size:12px}.wi-pending__item em{color:#64748b;margin-left:auto}.wi-x{border:0;background:none;color:#94a3b8;cursor:pointer;font-size:16px}.wi-doc-picker{display:inline-flex;align-items:center;gap:6px}.wi-doc-picker select{font:inherit;font-size:13px;font-weight:500;padding:6px 30px 6px 12px;border-radius:var(--wi-r-sm);border:1px solid var(--wi-chrome-line-2);background:var(--wi-chrome-2);color:var(--wi-chrome-ink);-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer;background-image:url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%239ba3b4' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 11px center;transition:background .15s var(--wi-ease),border-color .15s var(--wi-ease)}.wi-doc-picker select:hover{background-color:var(--wi-chrome-3);border-color:#ffffff38}.wi-modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:50;background:#0f172a8c;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);display:flex;align-items:center;justify-content:center;padding:24px}.wi-modal{background:#fff;border-radius:var(--wi-r-lg);padding:26px 28px;width:100%;max-width:560px;box-shadow:var(--wi-sh-3);animation:wi-modal-in .22s var(--wi-ease) both}@keyframes wi-modal-in{0%{opacity:0;transform:translateY(8px) scale(.98)}to{opacity:1;transform:none}}.wi-modal__title{margin:0 0 6px;font-size:var(--wi-text-xl);font-weight:700;letter-spacing:-.02em;color:var(--wi-ink)}.wi-modal__hint{margin:0 0 16px;color:var(--wi-ink-soft);font-size:var(--wi-text-sm);line-height:var(--wi-lh-base)}.wi-modal__field{display:flex;flex-direction:column;gap:5px;margin-bottom:14px;font-size:13px;font-weight:500;color:#334155}.wi-modal__field input,.wi-modal__field textarea{font:inherit;font-weight:400;padding:9px 11px;border:1px solid var(--wi-line);border-radius:var(--wi-r-sm);background:var(--wi-field);box-shadow:inset 0 1px 2px #0f172a0d;transition:border-color .15s var(--wi-ease),background .15s var(--wi-ease),box-shadow .15s var(--wi-ease)}.wi-modal__field input:hover,.wi-modal__field textarea:hover{border-color:#cbd5e1}.wi-modal__field input:focus,.wi-modal__field textarea:focus{background:#fff;box-shadow:0 0 0 3px var(--wi-ring)}.wi-modal__field textarea{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;resize:vertical}.wi-modal__error{color:#b91c1c;background:#fef2f2;padding:6px 10px;border-radius:6px;font-size:13px;margin-bottom:8px}.wi-modal__tabs{display:flex;gap:3px;background:var(--wi-field);padding:3px;border-radius:var(--wi-r-sm);margin-bottom:10px}.wi-modal__tab{flex:1;border:0;background:transparent;color:var(--wi-ink-soft);font:inherit;font-size:13px;font-weight:500;padding:8px;border-radius:7px;cursor:pointer;transition:background .15s var(--wi-ease),color .15s var(--wi-ease)}.wi-modal__tab.on{background:#fff;color:var(--wi-accent);font-weight:600;box-shadow:var(--wi-sh-1)}.wi-modal__mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.wi-modal__actions{display:flex;gap:8px;justify-content:flex-end;margin-top:8px}.wi-vsel{display:inline-flex;align-items:center;gap:6px;padding:0 4px 0 10px;height:30px;border-radius:999px;background:#ffffff0d;box-shadow:inset 0 0 0 1px var(--wi-chrome-line)}.wi-vsel__icon{color:var(--wi-chrome-ink-faint);display:grid;place-items:center;flex:0 0 auto}.wi-vsel select{font:inherit;font-size:var(--wi-text-sm);font-weight:600;letter-spacing:-.01em;padding:0 26px 0 4px;height:30px;border:0;border-radius:999px;background:transparent;color:var(--wi-chrome-ink);-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer;background-image:url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%239ba3b4' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 9px center}.wi-vsel:hover{background:#ffffff17}.wi-export{display:inline-flex;align-items:stretch;height:30px;border-radius:var(--wi-r-sm);overflow:hidden;background:var(--wi-chrome-2);box-shadow:inset 0 1px #ffffff0f,0 1px 2px #00000059}.wi-export__icon{display:grid;place-items:center;padding:0 9px;color:var(--wi-chrome-ink-soft)}.wi-export__seg{border:0;background:transparent;color:var(--wi-chrome-ink);cursor:pointer;font:inherit;font-size:var(--wi-text-sm);font-weight:600;padding:0 13px;letter-spacing:.01em;box-shadow:inset 1px 0 0 var(--wi-chrome-line);transition:background .14s var(--wi-ease),color .14s var(--wi-ease)}.wi-export__seg:hover:not(:disabled){background:var(--wi-accent);color:#fff}.wi-export__seg:active:not(:disabled){background:var(--wi-accent-h)}.wi-export__seg:disabled{opacity:.4;cursor:not-allowed}.wi-download{display:inline-flex;align-items:center;gap:7px;text-decoration:none;height:30px}.wi-download__icon{display:grid;place-items:center}.wi-download--disabled{opacity:.45;pointer-events:none;cursor:not-allowed;box-shadow:none}.wi-empty-hint{position:absolute;left:8px;z-index:3;pointer-events:none;display:flex;align-items:center;gap:6px;animation:wi-frame-in .5s var(--wi-ease) both}.wi-empty-hint__arrow{color:var(--wi-accent);flex:0 0 auto}.wi-empty-hint__text{font-family:Segoe Print,Bradley Hand,Comic Sans MS,Comic Sans,cursive;font-size:19px;line-height:1.2;color:var(--wi-accent);transform:rotate(-2deg);max-width:210px;text-shadow:0 1px 0 rgba(255,255,255,.6)}.wi-lock{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;display:flex;align-items:center;justify-content:center;background:#0f172a73;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.wi-lock__card{background:#fff;border-radius:14px;padding:28px 32px;min-width:320px;max-width:460px;text-align:center;box-shadow:0 20px 50px #0000004d}.wi-lock__msg{margin:14px 0 0;color:#0f172a;font-size:15px}.wi-lock__q{margin:0 0 16px;font-size:16px;font-weight:600;color:#0f172a}.wi-lock__opts{display:flex;flex-wrap:wrap;gap:8px;justify-content:center}.wi-lock__dismiss{display:block;margin:16px auto 0;border:0;background:none;color:#94a3b8;font-size:12px;text-decoration:underline;cursor:pointer}.wi-spinner{width:40px;height:40px;margin:0 auto;border-radius:50%;background:conic-gradient(from 0deg,#3b82f6,#7c3aed,#ec4899,#3b82f6);-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 4px),#000 calc(100% - 4px));mask:radial-gradient(farthest-side,transparent calc(100% - 4px),#000 calc(100% - 4px));animation:wi-spin .9s linear infinite}@keyframes wi-spin{to{transform:rotate(360deg)}}.wi-strip{display:flex;gap:5px;flex-wrap:wrap}.wi-chip{display:inline-flex;align-items:center;gap:5px;border:1px solid var(--wi-chrome-line-2);background:var(--wi-chrome-2);color:var(--wi-chrome-ink-soft);padding:4px 11px;border-radius:999px;cursor:pointer;font-size:12px;font-weight:500;transition:border-color .15s var(--wi-ease),color .15s var(--wi-ease),background .15s var(--wi-ease)}.wi-chip:hover{background:var(--wi-chrome-3);border-color:#ffffff38;color:var(--wi-chrome-ink)}.wi-chip--active{border-color:transparent;color:#fff;background:var(--wi-accent);box-shadow:0 0 0 1px #818cf866,0 2px 8px #4f46e573;animation:wi-chip-pop .26s cubic-bezier(.34,1.56,.64,1) both}@keyframes wi-chip-pop{0%{transform:scale(.9)}to{transform:scale(1)}}.wi-chip--head{font-weight:600}.wi-chip__head{font-size:8.5px;letter-spacing:.04em;text-transform:uppercase;background:#16a34a;color:#fff;padding:1px 5px;border-radius:4px}.wi-chip--active .wi-chip__head{background:#ffffff40}.wi-modal__disclosure{background:transparent;border:1px dashed #cbd5e1;color:#475569;font:inherit;font-size:13px;padding:10px 12px;border-radius:8px;cursor:pointer;text-align:left;width:100%;margin-bottom:12px;transition:border-color .15s,color .15s}.wi-modal__disclosure:hover{border-color:var(--wi-accent);color:var(--wi-accent)}.wi-modal__disclosure span{float:right;color:var(--wi-accent);font-weight:600}.wi-modal__optional{color:#94a3b8;font-weight:400;font-size:12px;margin-left:4px}.wi-paths{display:flex;flex-direction:column;gap:8px;margin-top:6px}.wi-paths__row{display:flex;gap:8px;align-items:center}.wi-paths__row input{flex:1 1 auto;min-width:0}.wi-paths__remove{flex:0 0 auto;width:32px;height:32px;border-radius:var(--wi-r-sm);border:1px solid var(--wi-line);background:var(--wi-surface);color:var(--wi-ink-soft);font-size:18px;line-height:1;cursor:pointer;transition:color .15s var(--wi-ease),border-color .15s var(--wi-ease),background .15s var(--wi-ease)}.wi-paths__remove:hover{color:#dc2626;border-color:#ef444466;background:#fef2f2}.wi-paths__add{align-self:flex-start;margin-top:8px;border:1px dashed var(--wi-line);background:transparent;color:var(--wi-accent);font:inherit;font-size:13px;font-weight:500;padding:7px 12px;border-radius:var(--wi-r-sm);cursor:pointer;transition:border-color .15s var(--wi-ease),background .15s var(--wi-ease)}.wi-paths__add:hover{border-color:var(--wi-accent);background:var(--wi-accent-soft)}.wi-gate{position:fixed;top:0;right:0;bottom:0;left:0;z-index:100;display:flex;align-items:center;justify-content:center;background:#020617d1;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}.wi-gate__panel{background:#0f172a;border:1px solid #334155;border-radius:16px;padding:32px 36px;max-width:560px;width:calc(100vw - 48px);color:#e2e8f0;box-shadow:0 30px 80px #00000080}.wi-gate__title{margin:0 0 8px;font-size:22px;color:#fff}.wi-gate__body{margin:0 0 18px;color:#cbd5e1;font-size:14px;line-height:1.5}.wi-gate__list{list-style:none;padding:0;margin:0 0 18px;display:grid;gap:6px}.wi-gate__item{display:grid;grid-template-columns:18px 1fr auto;align-items:center;gap:10px;padding:8px 12px;border-radius:8px;background:#1e293b99;border:1px solid #1e293b;font-size:13px}.wi-gate__item--ok{color:#86efac;border-color:#22c55e59}.wi-gate__item--miss{color:#fca5a5;border-color:#ef444459}.wi-gate__item code{color:inherit;font-size:13px}.wi-gate__dot{font-size:14px;line-height:1}.wi-gate__status{font-size:11px;text-transform:uppercase;letter-spacing:.04em;opacity:.85}.wi-gate__cta{margin:0 0 6px;color:#94a3b8;font-size:12px}.wi-gate__cmd{background:#020617;border:1px solid #1e293b;border-radius:8px;padding:12px 14px;margin:0 0 18px;overflow-x:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;color:#f1f5f9}.wi-gate__actions{display:flex;justify-content:flex-end}.wi-gate__warn{margin:12px 0 0;color:#fbbf24;font-size:12px}.wi-msg--thinking .wi-msg__text{display:inline-flex;align-items:center;gap:8px;opacity:.85}.wi-typing{display:inline-flex;gap:4px;align-items:center}.wi-typing span{width:6px;height:6px;border-radius:50%;background:linear-gradient(135deg,#a78bfa,#38bdf8);display:inline-block;animation:wi-typing-bounce 1.1s infinite ease-in-out}.wi-typing span:nth-child(2){animation-delay:.15s}.wi-typing span:nth-child(3){animation-delay:.3s}.wi-typing__label{font-style:italic;color:#94a3b8;font-size:12px}@keyframes wi-typing-bounce{0%,80%,to{transform:translateY(0);opacity:.5}40%{transform:translateY(-3px);opacity:1}}.wi-chat__pulse{width:14px;height:14px;border-radius:50%;background:linear-gradient(135deg,#a78bfa,#38bdf8);display:inline-block;box-shadow:0 0 #a78bfab3;animation:wi-pulse 1.4s infinite}@keyframes wi-pulse{0%{box-shadow:0 0 #a78bfa8c}70%{box-shadow:0 0 0 10px #a78bfa00}to{box-shadow:0 0 #a78bfa00}}
@@ -0,0 +1,13 @@
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.0" />
6
+ <title>wicked-interactive</title>
7
+ <script type="module" crossorigin src="/assets/index-Df5rc-Mm.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-Dq_AQHYX.css">
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ </body>
13
+ </html>
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "wicked-interactive",
3
+ "version": "0.4.0",
4
+ "description": "Interactive HTML & Presentation Builder with an in-browser feedback loop for business users.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "bin": {
11
+ "wicked-interactive": "bin/wicked-interactive.js"
12
+ },
13
+ "files": [
14
+ "bin/",
15
+ "src/",
16
+ "frontend/dist/",
17
+ ".claude-plugin/",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "test": "node --test \"test/*.test.js\"",
23
+ "acceptance": "npm run build --prefix frontend && node test/e2e.mjs",
24
+ "check:version": "node .github/scripts/check-version.mjs",
25
+ "version": "node .github/scripts/sync-plugin-version.mjs && git add .claude-plugin/plugin.json .claude-plugin/marketplace.json"
26
+ },
27
+ "dependencies": {
28
+ "cheerio": "^1.0.0",
29
+ "chokidar": "^4.0.0",
30
+ "express": "^4.21.0",
31
+ "js-yaml": "^4.1.0",
32
+ "playwright": "^1.60.0"
33
+ },
34
+ "engines": {
35
+ "node": ">=20"
36
+ },
37
+ "devDependencies": {
38
+ "puppeteer-core": "^23.11.1"
39
+ }
40
+ }
@@ -0,0 +1,124 @@
1
+ // feedback-schema.js — parse/serialize the _v{x}.md feedback file (ADR-0002).
2
+ //
3
+ // File shape:
4
+ // ---
5
+ // version: 3
6
+ // base_html: _v2.html
7
+ // timestamp: 2026-05-26T18:00:00Z
8
+ // author: jane # optional
9
+ // ---
10
+ //
11
+ // ## item: slide-0-heading-1
12
+ // - type: content-edit
13
+ // - instruction: Rename the title
14
+ // - before: Q2 Results
15
+ // - value: Q3 Results
16
+ //
17
+ // ## item: slide-0-paragraph-2
18
+ // - type: style-edit
19
+ // - before: ...
20
+ // - style: { color: "#c00", font-weight: bold }
21
+ // - class_add: [highlight]
22
+ //
23
+ // Per-type operation fields (ADR-0002 refinement):
24
+ // content-edit -> value
25
+ // style-edit -> style (map) and/or class_add[] / class_remove[]
26
+ // structural-change -> instruction (free text, LLM)
27
+
28
+ import yaml from "js-yaml";
29
+
30
+ export const TYPES = ["content-edit", "style-edit", "structural-change", "remove"];
31
+
32
+ const ITEM_HEADING = /^##\s+item:\s*(.+?)\s*$/;
33
+ const FIELD = /^-\s+([a-z_]+):\s*([\s\S]*)$/;
34
+
35
+ function parseScalar(raw) {
36
+ const t = raw.trim();
37
+ if (t === "") return "";
38
+ // Allow inline YAML for structured fields (maps/lists), fall back to string.
39
+ if (/^[[{]/.test(t)) {
40
+ try { return yaml.load(t); } catch { /* fall through */ }
41
+ }
42
+ return t;
43
+ }
44
+
45
+ /**
46
+ * Parse a feedback markdown string.
47
+ * @param {string} md
48
+ * @returns {{ frontmatter: object, items: Array<object> }}
49
+ */
50
+ export function parseFeedback(md) {
51
+ const fmMatch = md.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
52
+ if (!fmMatch) throw new Error("feedback file missing YAML frontmatter");
53
+ const frontmatter = yaml.load(fmMatch[1]) || {};
54
+ const body = fmMatch[2];
55
+
56
+ const items = [];
57
+ let current = null;
58
+ for (const line of body.split("\n")) {
59
+ const h = line.match(ITEM_HEADING);
60
+ if (h) {
61
+ if (current) items.push(finalizeItem(current));
62
+ current = { selector: h[1], _fields: {} };
63
+ continue;
64
+ }
65
+ if (!current) continue;
66
+ const f = line.match(FIELD);
67
+ if (f) current._fields[f[1]] = parseScalar(f[2]);
68
+ }
69
+ if (current) items.push(finalizeItem(current));
70
+
71
+ for (const it of items) validateItem(it);
72
+ return { frontmatter, items };
73
+ }
74
+
75
+ function finalizeItem({ selector, _fields }) {
76
+ return {
77
+ selector,
78
+ type: _fields.type,
79
+ instruction: _fields.instruction ?? null,
80
+ before: _fields.before ?? null,
81
+ value: _fields.value ?? null,
82
+ style: _fields.style ?? null,
83
+ class_add: normalizeList(_fields.class_add),
84
+ class_remove: normalizeList(_fields.class_remove),
85
+ };
86
+ }
87
+
88
+ function normalizeList(v) {
89
+ if (v == null) return null;
90
+ if (Array.isArray(v)) return v;
91
+ return String(v).split(",").map((s) => s.trim()).filter(Boolean);
92
+ }
93
+
94
+ function validateItem(it) {
95
+ if (!it.selector) throw new Error("feedback item missing selector");
96
+ if (!TYPES.includes(it.type)) {
97
+ throw new Error(`feedback item ${it.selector}: invalid type ${JSON.stringify(it.type)}`);
98
+ }
99
+ if (it.type === "content-edit" && it.value == null) {
100
+ throw new Error(`content-edit ${it.selector}: missing 'value'`);
101
+ }
102
+ if (it.type === "style-edit" && it.style == null && !it.class_add && !it.class_remove) {
103
+ throw new Error(`style-edit ${it.selector}: needs 'style', 'class_add', or 'class_remove'`);
104
+ }
105
+ if (it.type === "structural-change" && !it.instruction) {
106
+ throw new Error(`structural-change ${it.selector}: missing 'instruction'`);
107
+ }
108
+ }
109
+
110
+ /** Serialize a feedback object back to the _v{x}.md format. */
111
+ export function serializeFeedback({ frontmatter, items }) {
112
+ const fm = yaml.dump(frontmatter).trimEnd();
113
+ const blocks = items.map((it) => {
114
+ const lines = [`## item: ${it.selector}`, `- type: ${it.type}`];
115
+ if (it.instruction != null) lines.push(`- instruction: ${it.instruction}`);
116
+ if (it.before != null) lines.push(`- before: ${it.before}`);
117
+ if (it.value != null) lines.push(`- value: ${it.value}`);
118
+ if (it.style != null) lines.push(`- style: ${JSON.stringify(it.style)}`);
119
+ if (it.class_add) lines.push(`- class_add: [${it.class_add.join(", ")}]`);
120
+ if (it.class_remove) lines.push(`- class_remove: [${it.class_remove.join(", ")}]`);
121
+ return lines.join("\n");
122
+ });
123
+ return `---\n${fm}\n---\n\n${blocks.join("\n\n")}\n`;
124
+ }
@@ -0,0 +1,116 @@
1
+ // instrument.js — inject stable data-wid anchors into HTML (ADR-0001).
2
+ //
3
+ // data-wid format: `slide-{slideIndex}-{role}-{ordinal}`
4
+ // slideIndex — 0-based index of the nearest ancestor slide container
5
+ // (`section`, `[data-slide]`, `.slide`); 0 when there is none.
6
+ // role — derived from the tag (heading / paragraph / list-item / ...).
7
+ // ordinal — 1-based counter per (slideIndex, role).
8
+ //
9
+ // Stability (INV-1): an element that already carries a data-wid keeps it.
10
+
11
+ import * as cheerio from "cheerio";
12
+
13
+ export const DEFAULT_REVIEWABLE = [
14
+ "h1", "h2", "h3", "h4", "h5", "h6",
15
+ "p", "li", "blockquote", "figcaption",
16
+ "td", "th", "a", "button", "img",
17
+ // Author opt-in for composite cards (divs) and chiclets (spans). Anything tagged
18
+ // `data-card` becomes individually clickable and gets its own data-wid — without
19
+ // this, structural-change content (cards, chips, tiles) stays unreachable.
20
+ "[data-card]",
21
+ ];
22
+
23
+ const ROLE_BY_TAG = {
24
+ h1: "heading", h2: "heading", h3: "heading", h4: "heading", h5: "heading", h6: "heading",
25
+ p: "paragraph", li: "list-item", blockquote: "quote", figcaption: "caption",
26
+ td: "cell", th: "cell", a: "link", button: "button", img: "image",
27
+ };
28
+
29
+ const SLIDE_SELECTOR = "section, [data-slide], .slide";
30
+ // Containers that can be restyled/themed as a whole (ADR-0011).
31
+ const SECTION_SELECTOR = "section, header, [data-slide], .slide";
32
+
33
+ function roleFor(el) {
34
+ // `data-card` is explicit author intent — keep its own role so cards (divs) and
35
+ // chiclets (spans) end up with predictable, semantic wids like `slide-3-card-2`.
36
+ if (el?.attribs && el.attribs["data-card"] != null) return "card";
37
+ const tagName = el?.tagName || el?.name;
38
+ return ROLE_BY_TAG[tagName] || "block";
39
+ }
40
+
41
+ /**
42
+ * Instrument an HTML string with data-wid attributes.
43
+ * @param {string} html
44
+ * @param {object} [opts]
45
+ * @param {string[]} [opts.reviewable] selectors considered reviewable blocks
46
+ * @returns {{ html: string, ids: string[] }}
47
+ */
48
+ export function instrument(html, opts = {}) {
49
+ const reviewable = opts.reviewable || DEFAULT_REVIEWABLE;
50
+ const $ = cheerio.load(html, null, false);
51
+
52
+ // Index slide containers in document order.
53
+ const slides = $(SLIDE_SELECTOR).toArray();
54
+ const slideIndex = new Map();
55
+ slides.forEach((el, i) => slideIndex.set(el, i));
56
+
57
+ function nearestSlide(el) {
58
+ let cur = el.parent;
59
+ while (cur) {
60
+ if (slideIndex.has(cur)) return slideIndex.get(cur);
61
+ cur = cur.parent;
62
+ }
63
+ return 0;
64
+ }
65
+
66
+ const counters = new Map(); // `${slide}-${role}` -> n
67
+ const seen = new Set(); // pre-existing ids (preserve, avoid collision)
68
+ $("[data-wid]").each((_, el) => seen.add($(el).attr("data-wid")));
69
+
70
+ const ids = [];
71
+ $(reviewable.join(",")).each((_, el) => {
72
+ const $el = $(el);
73
+ const existing = $el.attr("data-wid");
74
+ if (existing) {
75
+ ids.push(existing);
76
+ return; // INV-1: never reassign
77
+ }
78
+ const slide = nearestSlide(el);
79
+ const role = roleFor(el);
80
+ const key = `${slide}-${role}`;
81
+ let n = (counters.get(key) || 0) + 1;
82
+ let wid = `slide-${slide}-${role}-${n}`;
83
+ while (seen.has(wid)) {
84
+ n += 1;
85
+ wid = `slide-${slide}-${role}-${n}`;
86
+ }
87
+ counters.set(key, n);
88
+ seen.add(wid);
89
+ $el.attr("data-wid", wid);
90
+ ids.push(wid);
91
+ });
92
+
93
+ // Anchor section/slide containers (ADR-0011). Additive: a `section-{i}` namespace that
94
+ // never collides with the `slide-...` block ids, and pre-existing ids are preserved.
95
+ const sectionIds = [];
96
+ let sec = 0;
97
+ $(SECTION_SELECTOR).each((_, el) => {
98
+ const $el = $(el);
99
+ const existing = $el.attr("data-wid");
100
+ if (existing) { sectionIds.push(existing); return; }
101
+ let wid = `section-${sec}`;
102
+ while (seen.has(wid)) { sec += 1; wid = `section-${sec}`; }
103
+ sec += 1;
104
+ seen.add(wid);
105
+ $el.attr("data-wid", wid);
106
+ sectionIds.push(wid);
107
+ });
108
+
109
+ return { html: $.html(), ids, sectionIds };
110
+ }
111
+
112
+ /** All data-wid values present in an HTML string, in document order. */
113
+ export function collectWids(html) {
114
+ const $ = cheerio.load(html, null, false);
115
+ return $("[data-wid]").map((_, el) => $(el).attr("data-wid")).toArray();
116
+ }
@@ -0,0 +1,140 @@
1
+ // regenerate.js — determinism-first hybrid regeneration engine (ADR-0003).
2
+ //
3
+ // content-edit / style-edit -> cheerio DOM surgery, no LLM.
4
+ // structural-change -> fragment-scoped LLM (opts.llm), increment 4.
5
+ //
6
+ // Guardrails:
7
+ // INV-2 every data-wid present in the input survives (per-item, revert on violation;
8
+ // plus a global safety net that throws).
9
+ // INV-3 only elements named in the feedback change; untargeted elements are untouched
10
+ // by construction (we mutate only the matched element).
11
+ // AC-10 stale-target detection: if `before` no longer matches, skip + flag.
12
+
13
+ import * as cheerio from "cheerio";
14
+ import { collectWids } from "./instrument.js";
15
+
16
+ export class Inv2Error extends Error {
17
+ constructor(missing) {
18
+ super(`INV-2 violation: data-wid(s) dropped during regeneration: ${missing.join(", ")}`);
19
+ this.name = "Inv2Error";
20
+ this.missing = missing;
21
+ }
22
+ }
23
+
24
+ const norm = (s) => String(s ?? "").replace(/\s+/g, " ").trim();
25
+
26
+ function widsUnder($, el) {
27
+ const out = [];
28
+ const own = $(el).attr("data-wid");
29
+ if (own) out.push(own);
30
+ $(el).find("[data-wid]").each((_, c) => out.push($(c).attr("data-wid")));
31
+ return out;
32
+ }
33
+
34
+ function applyStyle($el, styleMap) {
35
+ const existing = ($el.attr("style") || "")
36
+ .split(";").map((s) => s.trim()).filter(Boolean)
37
+ .reduce((acc, decl) => {
38
+ const i = decl.indexOf(":");
39
+ if (i > 0) acc[decl.slice(0, i).trim()] = decl.slice(i + 1).trim();
40
+ return acc;
41
+ }, {});
42
+ for (const [k, v] of Object.entries(styleMap)) existing[k] = String(v);
43
+ const serialized = Object.entries(existing).map(([k, v]) => `${k}: ${v}`).join("; ");
44
+ $el.attr("style", serialized);
45
+ }
46
+
47
+ /**
48
+ * Apply a parsed feedback object to the previous HTML, producing the next version.
49
+ * @param {string} prevHtml
50
+ * @param {{ items: Array<object> }} feedback
51
+ * @param {object} [opts]
52
+ * @param {(fragmentHtml: string, instruction: string) => Promise<string>} [opts.llm]
53
+ * @returns {Promise<{ html: string, applied: string[], rejected: Array<{selector:string,reason:string}>, stale: string[] }>}
54
+ */
55
+ export async function regenerate(prevHtml, feedback, opts = {}) {
56
+ const $ = cheerio.load(prevHtml, null, false);
57
+ const prevIds = collectWids(prevHtml);
58
+ const applied = [];
59
+ const rejected = [];
60
+ const stale = [];
61
+ const removed = []; // anchors intentionally deleted (exempt from the INV-2 net)
62
+
63
+ for (const item of feedback.items) {
64
+ const $el = $(`[data-wid="${item.selector}"]`);
65
+ if ($el.length === 0) {
66
+ rejected.push({ selector: item.selector, reason: "selector-not-found" });
67
+ continue;
68
+ }
69
+ const el = $el[0];
70
+
71
+ // AC-10 stale-target check.
72
+ if (item.before != null && norm($el.text()) !== norm(item.before)) {
73
+ stale.push(item.selector);
74
+ continue;
75
+ }
76
+
77
+ if (item.type === "content-edit") {
78
+ const prevInner = $el.html();
79
+ const before = widsUnder($, el);
80
+ $el.html(item.value);
81
+ const after = widsUnder($, el);
82
+ const dropped = before.filter((w) => !after.includes(w));
83
+ if (dropped.length) {
84
+ $el.html(prevInner); // revert
85
+ rejected.push({ selector: item.selector, reason: `inv2-would-drop-wids:${dropped.join(",")}` });
86
+ continue;
87
+ }
88
+ applied.push(item.selector);
89
+ } else if (item.type === "style-edit") {
90
+ if (item.style) applyStyle($el, item.style);
91
+ if (item.class_remove) item.class_remove.forEach((c) => $el.removeClass(c));
92
+ if (item.class_add) item.class_add.forEach((c) => $el.addClass(c));
93
+ applied.push(item.selector);
94
+ } else if (item.type === "remove") {
95
+ // Explicit structural removal (ADR-0003): the element and its subtree anchors go,
96
+ // and they're exempted from the INV-2 net below (this is intentional, not a drop).
97
+ removed.push(...widsUnder($, el));
98
+ $el.remove();
99
+ applied.push(item.selector);
100
+ } else if (item.type === "structural-change") {
101
+ if (typeof opts.llm !== "function") {
102
+ rejected.push({ selector: item.selector, reason: "structural-change-requires-llm" });
103
+ continue;
104
+ }
105
+ const fragmentBefore = $.html($el);
106
+ const subtreeBefore = widsUnder($, el);
107
+ let newFragment;
108
+ try {
109
+ newFragment = await opts.llm(fragmentBefore, item.instruction);
110
+ } catch (e) {
111
+ rejected.push({ selector: item.selector, reason: `llm-error:${e.message}` });
112
+ continue;
113
+ }
114
+ $el.replaceWith(newFragment);
115
+ // INV-2 for the LLM path: every wid in the fragment must survive.
116
+ const newSub = collectWids(newFragment);
117
+ const dropped = subtreeBefore.filter((w) => !newSub.includes(w));
118
+ if (dropped.length) {
119
+ // INV-2 violation on the LLM path: re-run without the offending item so the
120
+ // rest of the batch still applies, and the bad item is reported as rejected.
121
+ return regenerateExcluding(prevHtml, feedback, opts, item.selector);
122
+ }
123
+ applied.push(item.selector);
124
+ }
125
+ }
126
+
127
+ const html = $.html();
128
+ const present = collectWids(html);
129
+ const missing = prevIds.filter((w) => !present.includes(w) && !removed.includes(w));
130
+ if (missing.length) throw new Inv2Error(missing); // safety net — per-item guards should prevent this
131
+ return { html, applied, rejected, stale };
132
+ }
133
+
134
+ // On an LLM INV-2 violation, re-run without the offending item so the rest still apply.
135
+ async function regenerateExcluding(prevHtml, feedback, opts, badSelector) {
136
+ const filtered = { ...feedback, items: feedback.items.filter((i) => i.selector !== badSelector) };
137
+ const res = await regenerate(prevHtml, filtered, opts);
138
+ res.rejected.push({ selector: badSelector, reason: "inv2-llm-dropped-wids (excluded)" });
139
+ return res;
140
+ }
@@ -0,0 +1,79 @@
1
+ // theme.js — turn wicked-prezzie theme tokens into a per-version base <style> block
2
+ // (ADR-0011 theme step, ADR-0016 Slice C). PURE: no filesystem, no plugin discovery — token
3
+ // resolution from the prezzie plugin cache lives in service/theme-source.js. This keeps the
4
+ // core layer side-effect-free and unit-testable.
5
+ //
6
+ // The block uses element-level selectors so it is a genuine BASE layer: a document's own
7
+ // classed/inline styles win over it. It is injected FIRST (lowest precedence among equal
8
+ // specificity) and is idempotent — re-running replaces the previous block rather than
9
+ // stacking, so re-instrument after regenerate keeps a single, current theme block. It never
10
+ // touches data-wid anchors (INV-1/INV-2 safe — it only adds an anchor-free <style>).
11
+
12
+ import * as cheerio from "cheerio";
13
+
14
+ const THEME_MARKER = "data-wi-theme";
15
+
16
+ // Bundled fallback — a copy of prezzie's `corporate-light` tokens. Resilience against
17
+ // prezzie's on-disk layout changing, NOT plugin-optional behavior: the preflight still
18
+ // requires prezzie (ADR-0016). Guarantees the product themes consistently regardless.
19
+ export const DEFAULT_THEME = {
20
+ name: "corporate-light",
21
+ colors: {
22
+ background: "#FFFFFF", surface: "#F8FAFC", primary: "#1E3A5F", secondary: "#2563EB",
23
+ accent: "#0891B2", text_primary: "#1E293B", text_secondary: "#64748B", text_muted: "#94A3B8",
24
+ border: "#E2E8F0", success: "#059669", warning: "#D97706", error: "#DC2626",
25
+ },
26
+ fonts: { heading: "Calibri", body: "Calibri", mono: "Consolas" },
27
+ sizes: {
28
+ title: "44px", subtitle: "26px", heading: "34px", subheading: "22px",
29
+ body: "18px", caption: "13px", small: "11px",
30
+ },
31
+ spacing: { margin: "48px", gap_large: "32px", gap_medium: "24px", gap_small: "16px", gap_xs: "8px" },
32
+ card: { background: "#FFFFFF", border_radius: "8px", padding: "24px", shadow: "0 1px 3px rgba(0,0,0,0.1)" },
33
+ };
34
+
35
+ /** Turn a theme token object into a base CSS string (custom properties + gentle base rules). */
36
+ export function themeCss(tokens = DEFAULT_THEME) {
37
+ const c = tokens.colors || {}, f = tokens.fonts || {}, s = tokens.sizes || {}, card = tokens.card || {};
38
+ const vars = [
39
+ `--wi-bg:${c.background};`, `--wi-surface:${c.surface};`, `--wi-primary:${c.primary};`,
40
+ `--wi-secondary:${c.secondary};`, `--wi-accent:${c.accent};`, `--wi-text:${c.text_primary};`,
41
+ `--wi-text-secondary:${c.text_secondary};`, `--wi-muted:${c.text_muted};`, `--wi-border:${c.border};`,
42
+ `--wi-font-heading:${f.heading};`, `--wi-font-body:${f.body};`, `--wi-font-mono:${f.mono};`,
43
+ `--wi-size-title:${s.title};`, `--wi-size-heading:${s.heading};`, `--wi-size-body:${s.body};`,
44
+ `--wi-card-bg:${card.background};`, `--wi-card-radius:${card.border_radius};`,
45
+ `--wi-card-padding:${card.padding};`, `--wi-card-shadow:${card.shadow};`,
46
+ ].filter((v) => !v.includes("undefined")).join("");
47
+ return [
48
+ `:root{${vars}}`,
49
+ `body{font-family:var(--wi-font-body),system-ui,-apple-system,sans-serif;color:var(--wi-text);background:var(--wi-bg);}`,
50
+ `h1,h2,h3,h4,h5,h6{font-family:var(--wi-font-heading),system-ui,sans-serif;color:var(--wi-primary);line-height:1.2;}`,
51
+ `h1{font-size:var(--wi-size-title);}`,
52
+ `h2{font-size:var(--wi-size-heading);}`,
53
+ `a{color:var(--wi-secondary);}`,
54
+ `[data-card]{background:var(--wi-card-bg);border:1px solid var(--wi-border);border-radius:var(--wi-card-radius);padding:var(--wi-card-padding);box-shadow:var(--wi-card-shadow);}`,
55
+ ].join("\n");
56
+ }
57
+
58
+ /** The full `<style data-wi-theme="...">` block for a token object. */
59
+ export function themeStyleBlock(tokens = DEFAULT_THEME) {
60
+ return `<style ${THEME_MARKER}="${tokens.name || "theme"}">\n${themeCss(tokens)}\n</style>`;
61
+ }
62
+
63
+ /**
64
+ * Inject (or replace) the base theme block in an HTML fragment/document. Idempotent: any
65
+ * existing `style[data-wi-theme]` is removed first. Injected FIRST so it's the base layer.
66
+ * @param {string} html
67
+ * @param {object} [opts] { tokens?: object, theme:false to skip injection }
68
+ * @returns {string}
69
+ */
70
+ export function applyTheme(html, opts = {}) {
71
+ if (opts.theme === false) return html;
72
+ const tokens = opts.tokens || DEFAULT_THEME;
73
+ const $ = cheerio.load(html, null, false);
74
+ $(`style[${THEME_MARKER}]`).remove();
75
+ const block = themeStyleBlock(tokens);
76
+ if ($("head").length) $("head").prepend(block);
77
+ else $.root().prepend(block);
78
+ return $.html();
79
+ }