@tour-kit/studio 0.1.0 → 0.2.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.
Files changed (2) hide show
  1. package/dist/index.js +351 -31
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -140,6 +140,12 @@ var checklistTask = z4.object({
140
140
  ]).optional(),
141
141
  manualComplete: z4.boolean().optional()
142
142
  });
143
+ var checklistPosition = z4.enum([
144
+ "bottom-right",
145
+ "bottom-left",
146
+ "top-right",
147
+ "top-left"
148
+ ]);
143
149
  var checklistComponent = z4.object({
144
150
  kind: z4.literal("checklist"),
145
151
  id: z4.string(),
@@ -148,7 +154,15 @@ var checklistComponent = z4.object({
148
154
  icon: z4.string().optional(),
149
155
  tasks: z4.array(checklistTask).min(1),
150
156
  dismissible: z4.boolean().optional(),
151
- hideOnComplete: z4.boolean().optional()
157
+ hideOnComplete: z4.boolean().optional(),
158
+ // Display surface (Studio-authoring; projected to the rendered ELEMENT, not the
159
+ // SDK `ChecklistConfig` — the CLI strips both before emitting the config const).
160
+ // "launcher" (the DEFAULT when unset) = a floating corner launcher button that
161
+ // pops a compact panel (`<ChecklistLauncher>`) — the real-world checklist UX;
162
+ // "inline" = the in-flow `<Checklist>` panel. `position` places the launcher
163
+ // corner (default "bottom-right"); ignored for inline.
164
+ display: z4.enum(["launcher", "inline"]).optional(),
165
+ position: checklistPosition.optional()
152
166
  });
153
167
 
154
168
  // ../recipe/src/components.ts
@@ -246,7 +260,9 @@ var scaffold = z7.object({
246
260
  // ../recipe/src/schedule.ts
247
261
  import { z as z8 } from "zod";
248
262
  var dayOfWeek = z8.number().int().min(0).max(6);
263
+ var scheduleAppliesTo = z8.enum(["announcement", "survey"]);
249
264
  var scheduleDef = z8.object({
265
+ appliesTo: scheduleAppliesTo.optional(),
250
266
  enabled: z8.boolean().optional(),
251
267
  startAt: dateString.optional(),
252
268
  endAt: dateString.optional(),
@@ -487,19 +503,51 @@ var themeVariation = z10.object({
487
503
  theme: themeTokens
488
504
  });
489
505
  var recipeTheme = z10.object({
490
- variations: z10.array(themeVariation).length(1)
506
+ variations: z10.array(themeVariation).length(1),
507
+ // Opt-in CLI-codegen flag (NOT projected to the SDK): when true, `tourkit add`
508
+ // ALSO emits a `THEME_TAILWIND` const — a `plugin(({addBase}) => addBase(…))`
509
+ // object the user merges into `tailwind.config` — beside the always-emitted
510
+ // dual-channel CSS. The CSS path is universal (works in any host); this is for
511
+ // Tailwind users who prefer managing the tokens in their config.
512
+ tailwind: z10.boolean().optional()
491
513
  });
492
- var TOKEN_BRIDGE = {
493
- "--primary": "--tour-primary",
494
- "--primary-foreground": "--tour-primary-fg",
495
- "--popover": "--tour-card-bg",
496
- "--popover-foreground": "--tour-card-fg",
497
- "--muted-foreground": "--tour-muted-fg",
498
- "--border": "--tour-card-border",
499
- "--ring": "--tour-ring",
500
- "--radius": "--tour-card-radius"
501
- };
502
- var SCOPED_SELECTORS = ["[data-tour-step]"];
514
+ var THEME_TOKEN_ENTRIES = [
515
+ // Phase 32 — shadcn-vocab base (8)
516
+ ["--primary", "--tour-primary"],
517
+ ["--primary-foreground", "--tour-primary-fg"],
518
+ ["--popover", "--tour-card-bg"],
519
+ ["--popover-foreground", "--tour-card-fg"],
520
+ ["--muted-foreground", "--tour-muted-fg"],
521
+ ["--border", "--tour-card-border"],
522
+ ["--ring", "--tour-ring"],
523
+ ["--radius", "--tour-card-radius"],
524
+ // Phase 37 — buttons
525
+ ["--primary-hover", "--tour-primary-hover"],
526
+ ["--secondary", "--tour-secondary-bg"],
527
+ ["--secondary-foreground", "--tour-secondary-fg"],
528
+ ["--secondary-border", "--tour-secondary-border"],
529
+ ["--secondary-hover", "--tour-secondary-hover"],
530
+ // Phase 37 — card
531
+ ["--card-shadow", "--tour-card-shadow"],
532
+ ["--card-padding", "--tour-card-padding"],
533
+ ["--card-width", "--tour-card-width"],
534
+ // Phase 37 — backdrop / overlay
535
+ ["--overlay", "--tour-overlay-bg"],
536
+ ["--overlay-blur", "--tour-overlay-blur"],
537
+ // Phase 37 — typography
538
+ ["--font-family", "--tour-font-family"],
539
+ ["--font-size", "--tour-font-size"],
540
+ ["--line-height", "--tour-line-height"]
541
+ ];
542
+ var TOKEN_BRIDGE = Object.fromEntries(THEME_TOKEN_ENTRIES);
543
+ var SCOPED_SELECTORS = [
544
+ "[data-tour-step]",
545
+ "[data-survey-modal]",
546
+ "[data-survey-slideout]",
547
+ "[data-survey-banner]",
548
+ "[data-survey-popover]",
549
+ "[data-survey-inline]"
550
+ ];
503
551
 
504
552
  // ../recipe/src/recipe.ts
505
553
  var SUPPORTED_DEPS = {
@@ -635,12 +683,21 @@ var recipeSchemaV1 = z11.object({
635
683
  message: "free recipe must inject TourKitProvider"
636
684
  });
637
685
  for (const c of r.components)
638
- if ((c.kind === "announcement" || c.kind === "survey") && c.schedule && !r.schedules?.[c.schedule])
639
- ctx.addIssue({
640
- code: "custom",
641
- path: ["components"],
642
- message: `unknown schedule "${c.schedule}"`
643
- });
686
+ if ((c.kind === "announcement" || c.kind === "survey") && c.schedule) {
687
+ const sched = r.schedules?.[c.schedule];
688
+ if (!sched)
689
+ ctx.addIssue({
690
+ code: "custom",
691
+ path: ["components"],
692
+ message: `unknown schedule "${c.schedule}"`
693
+ });
694
+ else if (sched.appliesTo && sched.appliesTo !== c.kind)
695
+ ctx.addIssue({
696
+ code: "custom",
697
+ path: ["components"],
698
+ message: `schedule "${c.schedule}" applies to ${sched.appliesTo}, not this ${c.kind}`
699
+ });
700
+ }
644
701
  const selected = new Set(r.packages);
645
702
  r.components.forEach((c, i) => {
646
703
  const missing = requiredPackages(c).filter((p) => !selected.has(p));
@@ -659,6 +716,217 @@ var recipeSchemaV1 = z11.object({
659
716
  });
660
717
  });
661
718
 
719
+ // ../recipe/src/shim.ts
720
+ var EMBEDDED_DROP = /^(html|body|button)\b|utk-mock/;
721
+ function whereWrap(selector) {
722
+ const sel = selector.trim();
723
+ if (sel === ":root") return ":where(:root)";
724
+ const pseudoEl = sel.indexOf("::");
725
+ if (pseudoEl !== -1) {
726
+ return `:where(${sel.slice(0, pseudoEl)})${sel.slice(pseudoEl)}`;
727
+ }
728
+ return `:where(${sel})`;
729
+ }
730
+ function embeddedShimCss(css) {
731
+ const out = [];
732
+ let i = 0;
733
+ const n = css.length;
734
+ while (i < n) {
735
+ while (i < n && /\s/.test(css.charAt(i))) i++;
736
+ if (i >= n) break;
737
+ if (css.startsWith("/*", i)) {
738
+ const end = css.indexOf("*/", i + 2);
739
+ i = end === -1 ? n : end + 2;
740
+ continue;
741
+ }
742
+ const braceStart = css.indexOf("{", i);
743
+ if (braceStart === -1) break;
744
+ const selector = css.slice(i, braceStart).trim();
745
+ let depth = 1;
746
+ let j = braceStart + 1;
747
+ while (j < n && depth > 0) {
748
+ if (css.charAt(j) === "{") depth++;
749
+ else if (css.charAt(j) === "}") depth--;
750
+ j++;
751
+ }
752
+ const body = css.slice(braceStart + 1, j - 1);
753
+ i = j;
754
+ if (selector.startsWith("@keyframes")) {
755
+ out.push(`${selector}{${body}}`);
756
+ continue;
757
+ }
758
+ if (selector.startsWith("@media")) {
759
+ out.push(`${selector}{${embeddedShimCss(body)}}`);
760
+ continue;
761
+ }
762
+ if (EMBEDDED_DROP.test(selector)) continue;
763
+ out.push(`${selector.split(",").map(whereWrap).join(",")}{${body}}`);
764
+ }
765
+ return out.join("\n");
766
+ }
767
+ var PREVIEW_SHIM_CSS = `
768
+ :root{--background:#fff;--foreground:#0f172a;--popover:#fff;--popover-foreground:#0f172a;--card:#fff;--card-foreground:#0f172a;--primary:#2563eb;--primary-foreground:#fff;--secondary:#f1f5f9;--secondary-foreground:#0f172a;--muted:#f1f5f9;--muted-foreground:#64748b;--accent:#f1f5f9;--accent-foreground:#0f172a;--border:#e2e8f0;--ring:#2563eb;--destructive:#dc2626;--destructive-foreground:#fff;--radius:.5rem;--color-popover:var(--utk-popover, var(--popover));--color-border:var(--utk-border, var(--border))}
769
+ /* Button reset \u2014 FIRST so the zero-specificity :where() utility classes below
770
+ (bg-primary, border, px-4, text-sm, \u2026) WIN at equal specificity in the embedded
771
+ shim, while a bare/un-utilitied button still loses its default chrome (native
772
+ border + background). Host-safe: :where() is zero-specificity, so a host's own
773
+ button rules always win on host UI. This is the "no default button border" reset. */
774
+ :where(button){-webkit-appearance:none;appearance:none;background:transparent;border:0;padding:0;font:inherit;color:inherit;cursor:pointer}
775
+ /* overflow-x hidden: the faux-app surface never scrolls horizontally \u2014 mock
776
+ target boxes wrap and every SDK surface is fixed/portaled, so a horizontal
777
+ scrollbar in the canvas is only ever transient layout noise (e.g. a card
778
+ mid-position before floating-ui settles). Vertical scroll stays. */
779
+ html,body{margin:0;overflow-x:hidden}
780
+ body{font-family:ui-sans-serif,system-ui,-apple-system,sans-serif;background:#f8fafc;color:#0f172a;min-height:100vh}
781
+ #utk-mock-app{padding:24px;display:flex;flex-wrap:wrap;gap:16px;align-items:flex-start}
782
+ .utk-mock-box{min-width:150px;min-height:52px;padding:12px 16px;border:1px dashed #cbd5e1;border-radius:10px;background:#fff;display:flex;flex-direction:column;gap:2px;justify-content:center;font-size:13px;color:#334155;box-shadow:0 1px 2px rgba(15,23,42,.05)}
783
+ .utk-mock-box small{color:#94a3b8;font-size:11px}
784
+ .utk-mock-note{color:#64748b;font-size:14px;padding:24px}
785
+ .flex{display:flex}.inline-flex{display:inline-flex}.flex-col{flex-direction:column}.flex-1{flex:1 1 0%}
786
+ .items-center{align-items:center}.items-start{align-items:flex-start}
787
+ .justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}
788
+ .gap-1{gap:.25rem}.gap-2{gap:.5rem}.min-w-0{min-width:0}
789
+ .fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.z-50{z-index:50}
790
+ .h-2{height:.5rem}.h-6{height:1.5rem}.h-8{height:2rem}.w-80{width:20rem}.w-full{width:100%}
791
+ .p-4{padding:1rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}
792
+ .py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pt-2{padding-top:.5rem}
793
+ .mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mt-2{margin-top:.5rem}
794
+ .text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}
795
+ .font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-none{line-height:1}.tracking-tight{letter-spacing:-.015em}
796
+ .tabular-nums{font-variant-numeric:tabular-nums}.text-center{text-align:center}
797
+ .bg-popover{background-color:var(--utk-popover, var(--popover))}.text-popover-foreground{color:var(--utk-popover-foreground, var(--popover-foreground))}
798
+ .bg-primary{background-color:var(--utk-primary, var(--primary))}.text-primary-foreground{color:var(--utk-primary-foreground, var(--primary-foreground))}
799
+ .bg-muted{background-color:var(--utk-muted, var(--muted))}.text-muted-foreground{color:var(--utk-muted-foreground, var(--muted-foreground))}
800
+ .bg-transparent{background-color:transparent}.bg-accent{background-color:var(--utk-accent, var(--accent))}.text-foreground{color:var(--utk-foreground, var(--foreground))}
801
+ .border{border:1px solid var(--utk-border, var(--border))}
802
+ .rounded-lg{border-radius:var(--utk-radius, var(--radius))}.rounded-md{border-radius:calc(var(--utk-radius, var(--radius)) - .125rem)}.rounded-sm{border-radius:calc(var(--utk-radius, var(--radius)) - .25rem)}.rounded-full{border-radius:9999px}
803
+ .shadow{box-shadow:0 1px 3px rgba(0,0,0,.1),0 1px 2px rgba(0,0,0,.06)}
804
+ .shadow-lg{box-shadow:0 10px 15px -3px rgba(0,0,0,.12),0 4px 6px -4px rgba(0,0,0,.1)}
805
+ .opacity-70{opacity:.7}.transition-colors{transition:color .15s,background-color .15s,border-color .15s}.transition-opacity{transition:opacity .15s}
806
+ [class~='hover:bg-primary/90']:hover{background-color:#1d4ed8}
807
+ [class~='hover:bg-accent']:hover{background-color:var(--utk-accent, var(--accent))}
808
+ [class~='hover:text-accent-foreground']:hover{color:var(--utk-accent-foreground, var(--accent-foreground))}
809
+ [class~='hover:text-foreground']:hover{color:var(--utk-foreground, var(--foreground))}
810
+ [class~='hover:opacity-100']:hover{opacity:1}
811
+ [class~='hover:underline']:hover{text-decoration:underline}
812
+ [class~='disabled:opacity-50']:disabled{opacity:.5}
813
+ [class~='disabled:pointer-events-none']:disabled{pointer-events:none}
814
+ [class~='focus-visible:outline-none']:focus-visible,[class~='focus:outline-none']:focus{outline:none}
815
+ [class~='focus-visible:ring-2']:focus-visible,[class~='focus:ring-2']:focus{box-shadow:0 0 0 2px var(--utk-ring, var(--ring))}
816
+ [class~='focus-visible:ring-1']:focus-visible{box-shadow:0 0 0 1px var(--utk-ring, var(--ring))}
817
+ /* tour-kit hints v1.0.6 beacon + tooltip + close button (Phase 22A). Enumerated
818
+ from the cva class lists in the hints dist (P/q/F). The default un-variant
819
+ hotspot is a pulsing dot; the tooltip portals into <body>. */
820
+ .h-3{height:.75rem}.w-3{width:.75rem}.h-4{height:1rem}.w-4{width:1rem}
821
+ .border-2{border-width:2px;border-style:solid}.border-background{border-color:var(--utk-background, var(--background))}
822
+ .cursor-pointer{cursor:pointer}.p-3{padding:.75rem}.pr-4{padding-right:1rem}
823
+ .right-2{right:.5rem}.top-2{top:.5rem}.mb-1{margin-bottom:.25rem}
824
+ .shadow-md{box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1)}
825
+ [class~='max-w-[280px]']{max-width:280px}[class~='z-[9999]']{z-index:9999}
826
+ @keyframes tour-pulse{0%,100%{box-shadow:0 0 0 0 rgba(37,99,235,.7)}50%{box-shadow:0 0 0 8px rgba(37,99,235,0)}}
827
+ .animate-tour-pulse{animation:tour-pulse 1.5s ease-in-out infinite}
828
+ /* tour-kit checklists v0.13.9 \u2014 the inline <Checklist showProgress> panel (Phase 22B).
829
+ We render the panel directly (not the floating ChecklistLauncher), so this covers
830
+ the card container, header, progress bar, task rows + checkbox states, and the
831
+ all-complete footer. Enumerated from the cva lists in the checklists dist
832
+ (fe/Oe/Ue/ze container+header+content+complete \xB7 Ve/Ee/Le/pe progress \xB7 Me/De/He/_e task). */
833
+ .bg-card{background-color:var(--utk-card, var(--card))}.text-card-foreground{color:var(--utk-card-foreground, var(--card-foreground))}
834
+ .text-primary{color:var(--utk-primary, var(--primary))}.border-primary{border-color:var(--utk-primary, var(--primary))}
835
+ .shadow-sm{box-shadow:0 1px 2px rgba(0,0,0,.05)}
836
+ .border-b{border-bottom:1px solid var(--utk-border, var(--border))}.border-t{border-top:1px solid var(--utk-border, var(--border))}
837
+ .gap-3{gap:.75rem}.p-2{padding:.5rem}.w-5{width:1.25rem}.h-5{height:1.25rem}.h-full{height:100%}
838
+ .flex-shrink-0{flex-shrink:0}.overflow-hidden{overflow:hidden}.opacity-0{opacity:0}.opacity-50{opacity:.5}
839
+ .cursor-not-allowed{cursor:not-allowed}.line-through{text-decoration:line-through}
840
+ .line-clamp-2{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
841
+ .transition-all{transition:all .2s}.duration-300{transition-duration:.3s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}
842
+ [class~='bg-muted/30']{background-color:#f5f8fb}
843
+ [class~='hover:bg-muted/50']:hover{background-color:#e8eef6}
844
+ [class~='border-muted-foreground/30']{border-color:rgba(100,116,139,.3)}
845
+ [class~='hover:border-primary/50']:hover{border-color:rgba(37,99,235,.5)}
846
+ [class~='group']:hover [class~='group-hover:opacity-100']{opacity:1}
847
+ /* The FLOATING ChecklistLauncher button (closed state). The block above styles the
848
+ OPEN panel; the launcher's own size class (w-14 h-14) is launcher-specific and
849
+ otherwise unshimmed, so the closed button collapses to just its icon. bg-primary
850
+ / rounded-full / shadow-lg are already covered above. */
851
+ .w-14{width:3.5rem}.h-14{height:3.5rem}
852
+ /* The launcher's task-count BADGE \u2014 absolutely pinned to the button's top-right
853
+ corner (the negative offsets) so it never overlaps the centered icon. Without
854
+ these the badge fell back to its in-flow position on top of the glyph. */
855
+ .-top-1{top:-.25rem}.-right-1{right:-.25rem}.font-bold{font-weight:700}
856
+ .bg-destructive{background-color:var(--utk-destructive, var(--destructive))}.text-destructive-foreground{color:var(--utk-destructive-foreground, var(--destructive-foreground))}
857
+ /* The unlicensed watermark portals to <body> at bottom-right with a MAX z-index +
858
+ pointer-events:none (preview-only \u2014 it clears with a Pro license). A bottom-right
859
+ ChecklistLauncher lands in the same corner; the watermark can't be out-z-indexed,
860
+ so in the PREVIEW we lift it clear of the launcher. The rule overrides the
861
+ watermark's inline bottom:16px \u2014 an important rule beats a non-important inline
862
+ declaration even at the zero specificity the embedded :where() wrap gives it. */
863
+ [data-tourkit-watermark]{bottom:5rem!important}
864
+ /* tour-kit announcements v4.1.6 \u2014 modal/slideout/banner/toast/spotlight variants
865
+ (Phase 22C). The SDK ships Tailwind + tailwindcss-animate utilities; the
866
+ sandboxed iframe has no Tailwind build, so this shims the static layout/intent
867
+ classes each variant emits. Enter/exit animations live behind motion-safe:
868
+ variants we deliberately do NOT shim, so no enter animation runs \u2014 reduced-motion
869
+ safe by construction. Enumerated from the cva lists in the announcements dist. */
870
+ .bg-background{background-color:var(--utk-background, var(--background))}
871
+ .bg-secondary{background-color:var(--utk-secondary, var(--secondary))}.text-secondary-foreground{color:var(--utk-secondary-foreground, var(--secondary-foreground))}
872
+ .border-border{border-color:var(--utk-border, var(--border))}.border-0{border-width:0}
873
+ .grid{display:grid}.flex-row{flex-direction:row}.items-end{align-items:flex-end}
874
+ .gap-4{gap:1rem}.p-6{padding:1.5rem}.p-0{padding:0}.px-8{padding-left:2rem;padding-right:2rem}
875
+ .py-3{padding-top:.75rem;padding-bottom:.75rem}
876
+ .space-y-1>*+*{margin-top:.25rem}.space-y-4>*+*{margin-top:1rem}
877
+ .h-1{height:.25rem}.h-9{height:2.25rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.w-full{width:100%}
878
+ .max-w-sm{max-width:24rem}.max-w-md{max-width:28rem}.max-w-lg{max-width:32rem}.max-w-xl{max-width:36rem}
879
+ [class~='w-3/4']{width:75%}
880
+ .top-0{top:0}.bottom-0{bottom:0}.left-0{left:0}.right-0{right:0}
881
+ .top-4{top:1rem}.bottom-4{bottom:1rem}.left-4{left:1rem}.right-4{right:1rem}
882
+ .z-40{z-index:40}.whitespace-nowrap{white-space:nowrap}.pointer-events-auto{pointer-events:auto}
883
+ .opacity-90{opacity:.9}.underline-offset-4{text-underline-offset:4px}.text-lg{font-size:1.125rem;line-height:1.75rem}
884
+ [class~='bg-black/80']{background-color:rgba(0,0,0,.8)}
885
+ /* modal: the Radix Dialog content is fixed + centered (left/top 50% + translate). */
886
+ [class~='left-[50%]'][class~='top-[50%]']{left:50%;top:50%;transform:translate(-50%,-50%)}
887
+ [class~='max-w-[95vw]']{max-width:95vw}[class~='max-h-[95vh]']{max-height:95vh}
888
+ /* banner/toast intents */
889
+ .bg-blue-50{background-color:#eff6ff}.border-blue-200{border-color:#bfdbfe}.text-blue-900{color:#1e3a8a}
890
+ .bg-green-50{background-color:#f0fdf4}.border-green-200{border-color:#bbf7d0}.text-green-900{color:#14532d}.bg-green-600{background-color:#16a34a}
891
+ .bg-yellow-50{background-color:#fefce8}.border-yellow-200{border-color:#fef08a}.text-yellow-900{color:#713f12}.bg-yellow-600{background-color:#ca8a04}
892
+ .bg-red-50{background-color:#fef2f2}.border-red-200{border-color:#fecaca}.text-red-900{color:#7f1d1d}.bg-red-600{background-color:#dc2626}
893
+ /* tour-kit surveys v3.0.9 \u2014 modal/slideout/banner/popover/inline display modes +
894
+ the question controls (rating/text/select/boolean) and SurveyProgress
895
+ (Phase 30). Enumerated from the cva lists in the surveys src
896
+ (question/modal/slideout/banner variants). dark: variants and the Radix
897
+ data-[state] enter/exit animations are deliberately NOT shimmed (light theme;
898
+ reduced-motion safe by construction, like announcements). */
899
+ .text-base{font-size:1rem;line-height:1.5rem}.text-xl{font-size:1.25rem;line-height:1.75rem}
900
+ .px-6{padding-left:1.5rem;padding-right:1.5rem}
901
+ [class~='py-1.5']{padding-top:.375rem;padding-bottom:.375rem}
902
+ .h-12{height:3rem}.h-auto{height:auto}.w-2{width:.5rem}.w-8{width:2rem}.w-10{width:2.5rem}.w-12{width:3rem}
903
+ .border-l{border-left:1px solid var(--utk-border, var(--border))}.border-r{border-right:1px solid var(--utk-border, var(--border))}
904
+ .border-input{border-color:var(--utk-border, var(--border))}
905
+ [class~='bg-primary/10']{background-color:rgba(37,99,235,.1)}
906
+ .inset-y-0{top:0;bottom:0}
907
+ .flex-wrap{flex-wrap:wrap}.shrink-0{flex-shrink:0}
908
+ .resize-y{resize:vertical}
909
+ .text-destructive{color:#dc2626}.text-right{text-align:right}
910
+ .opacity-80{opacity:.8}.underline-offset-2{text-underline-offset:2px}
911
+ .duration-200{transition-duration:.2s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}
912
+ [class~='min-w-[280px]']{min-width:280px}
913
+ [class~='placeholder:text-muted-foreground']::placeholder{color:var(--utk-muted-foreground, var(--muted-foreground))}
914
+ .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}
915
+ @media (min-width:640px){[class~='sm:max-w-sm']{max-width:24rem}[class~='sm:max-w-md']{max-width:28rem}[class~='sm:max-w-lg']{max-width:32rem}[class~='sm:rounded-lg']{border-radius:var(--utk-radius, var(--radius))}}
916
+ /* preview-only inactive-schedule chrome (authoring legibility \u2014 NOT the SDK). The
917
+ variant still renders inside it so the canvas is never blank; the badge states
918
+ the gating truth (role=status for the a11y tree). The badge is fixed + top-z so
919
+ it stays visible even when the variant portals out of flow (modal/toast/slideout
920
+ render into <body>); the wrapper dims its in-flow content (banner/spotlight). */
921
+ .utk-ann-inactive{position:relative;margin:16px;padding:10px;border:1px dashed #cbd5e1;border-radius:10px;background:#fff;opacity:.85}
922
+ .utk-ann-inactive-badge{position:fixed;top:10px;left:50%;transform:translateX(-50%);z-index:100000;display:inline-flex;align-items:center;gap:6px;padding:4px 12px;border-radius:9999px;background:#fef08a;color:#713f12;font-size:12px;font-weight:600;box-shadow:0 2px 6px rgba(15,23,42,.18)}
923
+ .utk-ann-inactive-badge::before{content:"";width:7px;height:7px;border-radius:9999px;background:#a16207}
924
+ /* utk:focus selection highlight (Phase 22A) \u2014 flashes a synthesized target box. */
925
+ @keyframes utk-target-flash{0%{box-shadow:0 0 0 0 rgba(37,99,235,.5)}100%{box-shadow:0 0 0 6px rgba(37,99,235,0)}}
926
+ .utk-target-flash{animation:utk-target-flash .8s ease-out 1;border-color:#2563eb}
927
+ @media (prefers-reduced-motion:reduce){.animate-tour-pulse,.utk-target-flash{animation:none}}
928
+ `;
929
+
662
930
  // src/commands/add.ts
663
931
  import { defineCommand } from "citty";
664
932
  import { consola as consola3 } from "consola";
@@ -872,8 +1140,14 @@ function imports(recipe) {
872
1140
  if (kinds.has("hint"))
873
1141
  value.push(`import { Hint, HintsProvider } from "@tour-kit/hints";`);
874
1142
  if (kinds.has("checklist")) {
1143
+ const cls = recipe.components.filter(
1144
+ (c) => c.kind === "checklist"
1145
+ );
1146
+ const names = /* @__PURE__ */ new Set(["ChecklistProvider"]);
1147
+ for (const c of cls)
1148
+ names.add(c.display === "inline" ? "Checklist" : "ChecklistLauncher");
875
1149
  value.push(
876
- `import { Checklist, ChecklistProvider } from "@tour-kit/checklists";`
1150
+ `import { ${[...names].sort().join(", ")} } from "@tour-kit/checklists";`
877
1151
  );
878
1152
  types.push(`import type { ChecklistConfig } from "@tour-kit/checklists";`);
879
1153
  }
@@ -925,19 +1199,39 @@ function themeCssFor(tokens) {
925
1199
  const target = TOKEN_BRIDGE[k];
926
1200
  return target === void 0 ? [] : [[target, v]];
927
1201
  }).sort(([a], [b]) => cmp(a, b));
1202
+ const utk = entries.map(([k, v]) => [`--utk-${k.slice(2)}`, v]).sort(([a], [b]) => cmp(a, b));
928
1203
  const all = [...entries].sort(([a], [b]) => cmp(a, b));
929
1204
  return [
930
- cssBlock(":root", bridged),
931
- ...SCOPED_SELECTORS.map((sel) => cssBlock(sel, all))
1205
+ cssBlock(":root", [...bridged, ...utk]),
1206
+ cssBlock(SCOPED_SELECTORS.join(", "), all)
932
1207
  ].filter((b) => b !== "").join("\n");
933
1208
  }
1209
+ function themeTailwindFor(tokens) {
1210
+ const entries = Object.entries(tokens);
1211
+ const bridged = entries.flatMap(([k, v]) => {
1212
+ const target = TOKEN_BRIDGE[k];
1213
+ return target === void 0 ? [] : [[target, v]];
1214
+ }).sort(([a], [b]) => cmp(a, b));
1215
+ const all = [...entries].sort(([a], [b]) => cmp(a, b));
1216
+ const out = {};
1217
+ if (bridged.length > 0) out[":root"] = Object.fromEntries(bridged);
1218
+ for (const sel of SCOPED_SELECTORS)
1219
+ if (all.length > 0) out[sel] = Object.fromEntries(all);
1220
+ return out;
1221
+ }
934
1222
  function emitTheme(theme) {
935
- const css = themeCssFor(theme.variations[0]?.theme ?? {});
936
- return [
1223
+ const tokens = theme.variations[0]?.theme ?? {};
1224
+ const lines = [
937
1225
  "// theme: recipe-global ThemeProvider variations + the portal-bridging <style> css",
938
1226
  `export const THEME_VARIATIONS: ThemeVariation[] = ${serialize(theme.variations, 0)};`,
939
- `export const THEME_CSS = ${str(css)};`
940
- ].join("\n");
1227
+ `export const THEME_CSS = ${str(themeCssFor(tokens))};`
1228
+ ];
1229
+ if (theme.tailwind)
1230
+ lines.push(
1231
+ "// Tailwind: merge into tailwind.config \u2014 `plugin(({ addBase }) => addBase(THEME_TAILWIND))`",
1232
+ `export const THEME_TAILWIND = ${serialize(themeTailwindFor(tokens), 0)};`
1233
+ );
1234
+ return lines.join("\n");
941
1235
  }
942
1236
  var jsxAttr = (name, value) => ` ${name}={${serialize(value, 0)}}`;
943
1237
  var STEP_FIELDS = [
@@ -976,7 +1270,19 @@ function emitHint(c) {
976
1270
  return ` <Hint key={${serialize(c.id, 0)}} id={${serialize(c.id, 0)}}${attrs} />`;
977
1271
  }
978
1272
  function emitChecklistEl(c) {
979
- return ` <Checklist key={${serialize(c.id, 0)}} checklistId={${serialize(c.id, 0)}} showProgress />`;
1273
+ const id = serialize(c.id, 0);
1274
+ if (c.display === "inline")
1275
+ return ` <Checklist key={${id}} checklistId={${id}} showProgress />`;
1276
+ const position = c.position ?? "bottom-right";
1277
+ return [
1278
+ ` <ChecklistLauncher key={${id}} checklistId={${id}} position=${str(position)}>`,
1279
+ ` <svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">`,
1280
+ ` <rect x="8" y="2" width="8" height="4" rx="1" ry="1" />`,
1281
+ ` <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />`,
1282
+ ` <path d="m9 14 2 2 4-4" />`,
1283
+ ` </svg>`,
1284
+ ` </ChecklistLauncher>`
1285
+ ].join("\n");
980
1286
  }
981
1287
  function emitAnnouncementEl(c) {
982
1288
  const Comp = ANNOUNCEMENT_COMPONENT[c.variant];
@@ -1068,7 +1374,7 @@ ${body}
1068
1374
  );
1069
1375
  }`;
1070
1376
  }
1071
- var STRIP = /* @__PURE__ */ new Set(["selectorMeta"]);
1377
+ var STRIP = /* @__PURE__ */ new Set(["selectorMeta", "appliesTo", "display", "position"]);
1072
1378
  function clean(value) {
1073
1379
  if (value instanceof RawCode) return value;
1074
1380
  if (Array.isArray(value)) return value.map(clean);
@@ -1232,12 +1538,17 @@ function SurveyBody({ surveyId }: { surveyId: string }) {
1232
1538
  )}
1233
1539
  <div className="flex justify-end gap-2">
1234
1540
  {state.currentStep > 0 && (
1235
- <button type="button" onClick={() => survey.prevQuestion()}>
1541
+ <button
1542
+ type="button"
1543
+ className="inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium text-muted-foreground transition-colors"
1544
+ onClick={() => survey.prevQuestion()}
1545
+ >
1236
1546
  Back
1237
1547
  </button>
1238
1548
  )}
1239
1549
  <button
1240
1550
  type="button"
1551
+ className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors"
1241
1552
  onClick={() => (isLast ? survey.complete() : advance())}
1242
1553
  >
1243
1554
  {isLast ? "Done" : "Next"}
@@ -1272,6 +1583,7 @@ export const ${hookName(h.ref)}: ${h.signature} = () => {
1272
1583
  ${stubs}
1273
1584
  `;
1274
1585
  }
1586
+ var BASE_SHIM_CSS = embeddedShimCss(PREVIEW_SHIM_CSS);
1275
1587
  function expand(recipe) {
1276
1588
  const body = recipe.components.map((c) => emitComponent(c, recipe)).join("\n\n");
1277
1589
  const helpers = recipe.components.some((c) => c.kind === "survey") ? `
@@ -1282,13 +1594,21 @@ ${SURVEY_HELPERS}` : "";
1282
1594
  ${emitTheme(recipe.theme)}` : "";
1283
1595
  const contents = `${header(recipe.id, recipe.version)}
1284
1596
  "use client";
1597
+ import "./${recipe.id}.css";
1285
1598
  ${imports(recipe)}
1286
1599
 
1287
1600
  ${body}${helpers}${themeConsts}
1288
1601
 
1289
1602
  ${emitMount(recipe)}
1290
1603
  `;
1291
- const files = [{ path: `tours/${recipe.id}.tsx`, contents }];
1604
+ const files = [
1605
+ { path: `tours/${recipe.id}.tsx`, contents },
1606
+ {
1607
+ path: `tours/${recipe.id}.css`,
1608
+ contents: `${BASE_SHIM_CSS}
1609
+ `
1610
+ }
1611
+ ];
1292
1612
  if (recipe.scaffold.hooks.length > 0)
1293
1613
  files.push({
1294
1614
  path: `tours/${recipe.id}.hooks.ts`,
@@ -1735,7 +2055,7 @@ runMain(
1735
2055
  defineCommand6({
1736
2056
  meta: {
1737
2057
  name: "tourkit",
1738
- version: "0.1.0",
2058
+ version: "0.2.0",
1739
2059
  description: "User Tour Kit Studio CLI"
1740
2060
  },
1741
2061
  subCommands: { add, pull, verify, harden, init }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tour-kit/studio",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "User Tour Kit Studio CLI — detect framework, install packages, scaffold tours, AST-patch providers, verify and harden selectors.",
5
5
  "type": "module",
6
6
  "bin": {