@gridland/demo 0.2.20 → 0.2.22

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 (3) hide show
  1. package/dist/landing.js +180 -182
  2. package/dist/run.js +183 -185
  3. package/package.json +2 -1
package/dist/landing.js CHANGED
@@ -1,6 +1,3 @@
1
- // ../docs/components/landing/landing-app.tsx
2
- import { useState as useState10 } from "react";
3
-
4
1
  // ../ui/components/theme/themes.ts
5
2
  var darkTheme = {
6
3
  primary: "#FF71CE",
@@ -317,6 +314,14 @@ function PromptInputStatusText() {
317
314
  if (status !== "error") return null;
318
315
  return /* @__PURE__ */ jsx16("text", { children: /* @__PURE__ */ jsx16("span", { style: textStyle({ fg: theme.error }), children: errorText }) });
319
316
  }
317
+ function PromptInputModel() {
318
+ const { model, theme } = usePromptInput();
319
+ if (!model) return null;
320
+ return /* @__PURE__ */ jsxs10("text", { dim: true, color: theme.muted, children: [
321
+ "model: ",
322
+ model
323
+ ] });
324
+ }
320
325
  function PromptInput({
321
326
  value: controlledValue,
322
327
  defaultValue = "",
@@ -337,7 +342,8 @@ function PromptInput({
337
342
  getSuggestions: customGetSuggestions,
338
343
  maxSuggestions = 5,
339
344
  enableHistory = true,
340
- showDividers = false,
345
+ model,
346
+ showDividers = true,
341
347
  useKeyboard: useKeyboardProp,
342
348
  children
343
349
  }) {
@@ -532,6 +538,7 @@ function PromptInput({
532
538
  sugIdx,
533
539
  maxSuggestions,
534
540
  errorText,
541
+ model,
535
542
  theme
536
543
  };
537
544
  if (children) {
@@ -542,6 +549,7 @@ function PromptInput({
542
549
  /* @__PURE__ */ jsx16(PromptInputSuggestions, {}),
543
550
  /* @__PURE__ */ jsx16(PromptInputTextarea, {}),
544
551
  /* @__PURE__ */ jsx16(PromptInputStatusText, {}),
552
+ /* @__PURE__ */ jsx16(PromptInputModel, {}),
545
553
  showDividers && /* @__PURE__ */ jsx16(PromptInputDivider, {})
546
554
  ] }) });
547
555
  }
@@ -550,6 +558,7 @@ PromptInput.Suggestions = PromptInputSuggestions;
550
558
  PromptInput.Submit = PromptInputSubmit;
551
559
  PromptInput.Divider = PromptInputDivider;
552
560
  PromptInput.StatusText = PromptInputStatusText;
561
+ PromptInput.Model = PromptInputModel;
553
562
 
554
563
  // ../ui/components/chat/chat.tsx
555
564
  import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
@@ -778,7 +787,7 @@ Message.Footer = MessageFooter;
778
787
  import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
779
788
 
780
789
  // ../ui/components/breakpoints/use-breakpoints.ts
781
- import { useTerminalDimensions } from "@gridland/web";
790
+ import { useTerminalDimensions } from "@gridland/core";
782
791
  var BREAKPOINTS = {
783
792
  tiny: 40,
784
793
  narrow: 60,
@@ -796,11 +805,111 @@ function useBreakpoints() {
796
805
  };
797
806
  }
798
807
 
808
+ // ../docs/components/landing/landing-app.tsx
809
+ import { useCallback as useCallback3, useRef as useRef8, useState as useState10 } from "react";
810
+
811
+ // ../docs/components/landing/about-modal.tsx
812
+ import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
813
+ function AboutModal({ onClose, useKeyboard }) {
814
+ const theme = useTheme();
815
+ return /* @__PURE__ */ jsx21(Modal, { title: "About Gridland", useKeyboard, onClose, children: /* @__PURE__ */ jsxs15("box", { paddingX: 1, flexDirection: "column", gap: 1, children: [
816
+ /* @__PURE__ */ jsx21("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "What is Gridland?" }),
817
+ /* @__PURE__ */ jsx21("text", { children: "Gridland renders terminal UIs to HTML5 Canvas with React." }),
818
+ /* @__PURE__ */ jsx21("text", { children: "No xterm.js. No terminal emulator. Just pixels." }),
819
+ /* @__PURE__ */ jsx21("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "Features" }),
820
+ /* @__PURE__ */ jsxs15("text", { children: [
821
+ /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
822
+ "\u2022",
823
+ " "
824
+ ] }),
825
+ "Canvas-rendered TUI components"
826
+ ] }),
827
+ /* @__PURE__ */ jsxs15("text", { children: [
828
+ /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
829
+ "\u2022",
830
+ " "
831
+ ] }),
832
+ "React reconciler with JSX"
833
+ ] }),
834
+ /* @__PURE__ */ jsxs15("text", { children: [
835
+ /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
836
+ "\u2022",
837
+ " "
838
+ ] }),
839
+ "Yoga flexbox layout engine"
840
+ ] }),
841
+ /* @__PURE__ */ jsxs15("text", { children: [
842
+ /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
843
+ "\u2022",
844
+ " "
845
+ ] }),
846
+ "Keyboard, mouse, and clipboard support"
847
+ ] }),
848
+ /* @__PURE__ */ jsxs15("text", { children: [
849
+ /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
850
+ "\u2022",
851
+ " "
852
+ ] }),
853
+ "Next.js and Vite plugins"
854
+ ] }),
855
+ /* @__PURE__ */ jsx21("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "Tech Stack" }),
856
+ /* @__PURE__ */ jsx21("text", { children: "React + opentui engine + yoga-layout + HTML5 Canvas" }),
857
+ /* @__PURE__ */ jsx21("text", { style: textStyle({ dim: true }), children: "Press q to close" })
858
+ ] }) });
859
+ }
860
+
861
+ // ../docs/components/landing/install-box.tsx
862
+ import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
863
+ function InstallBox() {
864
+ const theme = useTheme();
865
+ return /* @__PURE__ */ jsx22(
866
+ "box",
867
+ {
868
+ border: true,
869
+ borderStyle: "rounded",
870
+ borderColor: theme.border,
871
+ paddingX: 1,
872
+ flexDirection: "column",
873
+ flexShrink: 0,
874
+ children: /* @__PURE__ */ jsxs16("text", { children: [
875
+ /* @__PURE__ */ jsx22("span", { style: textStyle({ dim: true }), children: "$ " }),
876
+ /* @__PURE__ */ jsx22("span", { style: textStyle({ bold: true }), children: "bun create " }),
877
+ /* @__PURE__ */ jsx22("span", { style: textStyle({ fg: theme.accent }), children: "gridland" })
878
+ ] })
879
+ }
880
+ );
881
+ }
882
+
883
+ // ../docs/components/landing/links-box.tsx
884
+ import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
885
+ var UNDERLINE3 = 1 << 3;
886
+ function LinksBox() {
887
+ const theme = useTheme();
888
+ return /* @__PURE__ */ jsx23(
889
+ "box",
890
+ {
891
+ border: true,
892
+ borderStyle: "rounded",
893
+ borderColor: theme.border,
894
+ paddingX: 1,
895
+ flexDirection: "column",
896
+ flexShrink: 0,
897
+ children: /* @__PURE__ */ jsxs17("text", { children: [
898
+ /* @__PURE__ */ jsx23("span", { children: "\u{1F431}" }),
899
+ /* @__PURE__ */ jsx23("a", { href: "https://github.com/cjroth/gridland", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " GitHub" }),
900
+ /* @__PURE__ */ jsx23("span", { children: " " }),
901
+ /* @__PURE__ */ jsx23("span", { children: "\u{1F4D6}" }),
902
+ /* @__PURE__ */ jsx23("a", { href: "/docs", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " Docs" })
903
+ ] })
904
+ }
905
+ );
906
+ }
907
+
799
908
  // ../docs/components/landing/logo.tsx
800
909
  import { useState as useState8, useEffect as useEffect3, useRef as useRef6, useMemo as useMemo4 } from "react";
801
910
  import figlet from "figlet";
802
911
  import ansiShadow from "figlet/importable-fonts/ANSI Shadow.js";
803
- import { Fragment as Fragment6, jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
912
+ import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
804
913
  figlet.parseFont("ANSI Shadow", ansiShadow);
805
914
  function makeArt(text) {
806
915
  return figlet.textSync(text, { font: "ANSI Shadow" }).split("\n").filter((l) => l.trimEnd().length > 0).join("\n");
@@ -833,9 +942,9 @@ function RevealGradient({ children, revealCol }) {
833
942
  const gradientColors = GRADIENTS.instagram;
834
943
  const lines = children.split("\n");
835
944
  const maxLength = Math.max(...lines.map((l) => l.length));
836
- if (maxLength === 0) return /* @__PURE__ */ jsx21("text", { children });
945
+ if (maxLength === 0) return /* @__PURE__ */ jsx24("text", { children });
837
946
  const hexColors = useMemo4(() => generateGradient(gradientColors, maxLength), [maxLength]);
838
- return /* @__PURE__ */ jsx21("box", { position: "relative", width: maxLength, height: lines.length, shouldFill: false, children: lines.map((line, lineIndex) => {
947
+ return /* @__PURE__ */ jsx24("box", { position: "relative", width: maxLength, height: lines.length, shouldFill: false, children: lines.map((line, lineIndex) => {
839
948
  const runs = [];
840
949
  let current = null;
841
950
  for (let i = 0; i < line.length; i++) {
@@ -855,14 +964,14 @@ function RevealGradient({ children, revealCol }) {
855
964
  }
856
965
  }
857
966
  if (current) runs.push(current);
858
- return runs.map((run, runIndex) => /* @__PURE__ */ jsx21(
967
+ return runs.map((run, runIndex) => /* @__PURE__ */ jsx24(
859
968
  "box",
860
969
  {
861
970
  position: "absolute",
862
971
  top: lineIndex,
863
972
  left: run.start,
864
973
  shouldFill: false,
865
- children: /* @__PURE__ */ jsx21("text", { shouldFill: false, children: run.chars.map((char, ci) => /* @__PURE__ */ jsx21(
974
+ children: /* @__PURE__ */ jsx24("text", { shouldFill: false, children: run.chars.map((char, ci) => /* @__PURE__ */ jsx24(
866
975
  "span",
867
976
  {
868
977
  style: { fg: hexColors[run.start + ci] },
@@ -884,20 +993,20 @@ function Logo({ compact, narrow, mobile }) {
884
993
  const maxWidth = compact ? 8 : narrow ? 40 : 62;
885
994
  const revealCol = Math.round(revealProgress * (maxWidth + 4)) - 2;
886
995
  const taglineOpacity = Math.max(0, Math.min(1, (progress - 0.7) / 0.3));
887
- const subtitle = /* @__PURE__ */ jsxs15(Fragment6, { children: [
888
- /* @__PURE__ */ jsx21("text", { children: " " }),
889
- /* @__PURE__ */ jsx21("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs15("text", { style: textStyle({ fg: "#d4b0e8" }), opacity: taglineOpacity, wrapMode: "word", textAlign: "center", width: "100%", shouldFill: false, children: [
996
+ const subtitle = /* @__PURE__ */ jsxs18(Fragment6, { children: [
997
+ /* @__PURE__ */ jsx24("text", { children: " " }),
998
+ /* @__PURE__ */ jsx24("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs18("text", { style: textStyle({ fg: "#d4b0e8" }), opacity: taglineOpacity, wrapMode: "word", textAlign: "center", width: "100%", shouldFill: false, children: [
890
999
  "A framework for building terminal apps, built on ",
891
- /* @__PURE__ */ jsx21("a", { href: "https://opentui.com", style: { attributes: 72, fg: "#d4b0e8" }, children: "OpenTUI" }),
1000
+ /* @__PURE__ */ jsx24("a", { href: "https://opentui.com", style: { attributes: 72, fg: "#d4b0e8" }, children: "OpenTUI" }),
892
1001
  " + React." + (mobile ? " " : "\n") + "(Gridland apps, like this website, work in the browser and terminal.)"
893
1002
  ] }) })
894
1003
  ] });
895
1004
  if (!isBrowser) {
896
1005
  const art = compact ? "gridland" : narrow ? gridArt + "\n" + landArt : fullArt;
897
- return /* @__PURE__ */ jsxs15("box", { flexDirection: "column", flexShrink: 0, width: "100%", alignItems: "center", children: [
898
- /* @__PURE__ */ jsx21(Gradient, { name: "instagram", children: art }),
899
- /* @__PURE__ */ jsx21("text", { children: " " }),
900
- /* @__PURE__ */ jsx21("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs15("text", { style: textStyle({ fg: "#d4b0e8" }), shouldFill: false, children: [
1006
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexShrink: 0, width: "100%", alignItems: "center", children: [
1007
+ /* @__PURE__ */ jsx24(Gradient, { name: "instagram", children: art }),
1008
+ /* @__PURE__ */ jsx24("text", { children: " " }),
1009
+ /* @__PURE__ */ jsx24("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs18("text", { style: textStyle({ fg: "#d4b0e8" }), shouldFill: false, children: [
901
1010
  "A framework for building terminal apps, built on OpenTUI + React.",
902
1011
  "\n",
903
1012
  "(Gridland apps, like this website, work in the browser and terminal.)"
@@ -905,73 +1014,26 @@ function Logo({ compact, narrow, mobile }) {
905
1014
  ] });
906
1015
  }
907
1016
  if (compact) {
908
- return /* @__PURE__ */ jsxs15("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
909
- /* @__PURE__ */ jsx21("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx21("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx21(RevealGradient, { revealCol, children: "gridland" }) }) }),
1017
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1018
+ /* @__PURE__ */ jsx24("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx24("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx24(RevealGradient, { revealCol, children: "gridland" }) }) }),
910
1019
  subtitle
911
1020
  ] });
912
1021
  }
913
1022
  if (narrow) {
914
- return /* @__PURE__ */ jsxs15("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
915
- /* @__PURE__ */ jsx21("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsxs15("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: [
916
- /* @__PURE__ */ jsx21(RevealGradient, { revealCol, children: gridArt }),
917
- /* @__PURE__ */ jsx21(RevealGradient, { revealCol, children: landArt })
1023
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1024
+ /* @__PURE__ */ jsx24("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsxs18("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: [
1025
+ /* @__PURE__ */ jsx24(RevealGradient, { revealCol, children: gridArt }),
1026
+ /* @__PURE__ */ jsx24(RevealGradient, { revealCol, children: landArt })
918
1027
  ] }) }),
919
1028
  subtitle
920
1029
  ] });
921
1030
  }
922
- return /* @__PURE__ */ jsxs15("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
923
- /* @__PURE__ */ jsx21("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx21("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx21(RevealGradient, { revealCol, children: fullArt }) }) }),
1031
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1032
+ /* @__PURE__ */ jsx24("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx24("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx24(RevealGradient, { revealCol, children: fullArt }) }) }),
924
1033
  subtitle
925
1034
  ] });
926
1035
  }
927
1036
 
928
- // ../docs/components/landing/install-box.tsx
929
- import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
930
- function InstallBox() {
931
- const theme = useTheme();
932
- return /* @__PURE__ */ jsx22(
933
- "box",
934
- {
935
- border: true,
936
- borderStyle: "rounded",
937
- borderColor: theme.border,
938
- paddingX: 1,
939
- flexDirection: "column",
940
- flexShrink: 0,
941
- children: /* @__PURE__ */ jsxs16("text", { children: [
942
- /* @__PURE__ */ jsx22("span", { style: textStyle({ dim: true }), children: "$ " }),
943
- /* @__PURE__ */ jsx22("span", { style: textStyle({ bold: true }), children: "bun create " }),
944
- /* @__PURE__ */ jsx22("span", { style: textStyle({ fg: theme.accent }), children: "gridland" })
945
- ] })
946
- }
947
- );
948
- }
949
-
950
- // ../docs/components/landing/links-box.tsx
951
- import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
952
- var UNDERLINE3 = 1 << 3;
953
- function LinksBox() {
954
- const theme = useTheme();
955
- return /* @__PURE__ */ jsx23(
956
- "box",
957
- {
958
- border: true,
959
- borderStyle: "rounded",
960
- borderColor: theme.border,
961
- paddingX: 1,
962
- flexDirection: "column",
963
- flexShrink: 0,
964
- children: /* @__PURE__ */ jsxs17("text", { children: [
965
- /* @__PURE__ */ jsx23("span", { children: "\u{1F431}" }),
966
- /* @__PURE__ */ jsx23("a", { href: "https://github.com/cjroth/gridland", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " GitHub" }),
967
- /* @__PURE__ */ jsx23("span", { children: " " }),
968
- /* @__PURE__ */ jsx23("span", { children: "\u{1F4D6}" }),
969
- /* @__PURE__ */ jsx23("a", { href: "/docs", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " Docs" })
970
- ] })
971
- }
972
- );
973
- }
974
-
975
1037
  // ../docs/components/landing/matrix-background.tsx
976
1038
  import { useMemo as useMemo5 } from "react";
977
1039
 
@@ -1055,7 +1117,7 @@ function useMatrix(width, height) {
1055
1117
  }
1056
1118
 
1057
1119
  // ../docs/components/landing/matrix-background.tsx
1058
- import { jsx as jsx24 } from "react/jsx-runtime";
1120
+ import { jsx as jsx25 } from "react/jsx-runtime";
1059
1121
  var MUTE_LEVELS = [0.12, 0.18, 0.24, 0.3, 0.38];
1060
1122
  var BG = hexToRgb("#1a1a2e");
1061
1123
  function buildMutedColors(baseHex) {
@@ -1082,15 +1144,15 @@ function MatrixBackground({ width, height, clearRect, clearRects }) {
1082
1144
  () => columnColors.map(buildMutedColors),
1083
1145
  [columnColors]
1084
1146
  );
1085
- return /* @__PURE__ */ jsx24("box", { flexDirection: "column", children: grid.map((row, y) => /* @__PURE__ */ jsx24("text", { children: row.map((cell, x) => {
1147
+ return /* @__PURE__ */ jsx25("box", { flexDirection: "column", children: grid.map((row, y) => /* @__PURE__ */ jsx25("text", { children: row.map((cell, x) => {
1086
1148
  const inClearRect = clearRect && y >= clearRect.top && y < clearRect.top + clearRect.height && x >= clearRect.left && x < clearRect.left + clearRect.width || clearRects && clearRects.some(
1087
1149
  (r) => y >= r.top && y < r.top + r.height && x >= r.left && x < r.left + r.width
1088
1150
  );
1089
1151
  const mutedColors = columnMutedColors[x];
1090
1152
  if (cell === " " || inClearRect || !mutedColors) {
1091
- return /* @__PURE__ */ jsx24("span", { children: " " }, x);
1153
+ return /* @__PURE__ */ jsx25("span", { children: " " }, x);
1092
1154
  }
1093
- return /* @__PURE__ */ jsx24(
1155
+ return /* @__PURE__ */ jsx25(
1094
1156
  "span",
1095
1157
  {
1096
1158
  style: {
@@ -1103,62 +1165,34 @@ function MatrixBackground({ width, height, clearRect, clearRects }) {
1103
1165
  }) }, y)) });
1104
1166
  }
1105
1167
 
1106
- // ../docs/components/landing/about-modal.tsx
1107
- import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
1108
- function AboutModal({ onClose, useKeyboard }) {
1109
- const theme = useTheme();
1110
- return /* @__PURE__ */ jsx25(Modal, { title: "About Gridland", useKeyboard, onClose, children: /* @__PURE__ */ jsxs18("box", { paddingX: 1, flexDirection: "column", gap: 1, children: [
1111
- /* @__PURE__ */ jsx25("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "What is Gridland?" }),
1112
- /* @__PURE__ */ jsx25("text", { children: "Gridland renders terminal UIs to HTML5 Canvas with React." }),
1113
- /* @__PURE__ */ jsx25("text", { children: "No xterm.js. No terminal emulator. Just pixels." }),
1114
- /* @__PURE__ */ jsx25("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "Features" }),
1115
- /* @__PURE__ */ jsxs18("text", { children: [
1116
- /* @__PURE__ */ jsxs18("span", { style: textStyle({ dim: true }), children: [
1117
- "\u2022",
1118
- " "
1119
- ] }),
1120
- "Canvas-rendered TUI components"
1121
- ] }),
1122
- /* @__PURE__ */ jsxs18("text", { children: [
1123
- /* @__PURE__ */ jsxs18("span", { style: textStyle({ dim: true }), children: [
1124
- "\u2022",
1125
- " "
1126
- ] }),
1127
- "React reconciler with JSX"
1128
- ] }),
1129
- /* @__PURE__ */ jsxs18("text", { children: [
1130
- /* @__PURE__ */ jsxs18("span", { style: textStyle({ dim: true }), children: [
1131
- "\u2022",
1132
- " "
1133
- ] }),
1134
- "Yoga flexbox layout engine"
1135
- ] }),
1136
- /* @__PURE__ */ jsxs18("text", { children: [
1137
- /* @__PURE__ */ jsxs18("span", { style: textStyle({ dim: true }), children: [
1138
- "\u2022",
1139
- " "
1140
- ] }),
1141
- "Keyboard, mouse, and clipboard support"
1142
- ] }),
1143
- /* @__PURE__ */ jsxs18("text", { children: [
1144
- /* @__PURE__ */ jsxs18("span", { style: textStyle({ dim: true }), children: [
1145
- "\u2022",
1146
- " "
1147
- ] }),
1148
- "Next.js and Vite plugins"
1149
- ] }),
1150
- /* @__PURE__ */ jsx25("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "Tech Stack" }),
1151
- /* @__PURE__ */ jsx25("text", { children: "React + opentui engine + yoga-layout + HTML5 Canvas" }),
1152
- /* @__PURE__ */ jsx25("text", { style: textStyle({ dim: true }), children: "Press q to close" })
1153
- ] }) });
1154
- }
1155
-
1156
1168
  // ../docs/components/landing/landing-app.tsx
1157
1169
  import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
1170
+ var DEMO_RESPONSES = [
1171
+ "Gridland is a framework for building terminal apps with React. It works in both the browser and terminal!",
1172
+ "You can get started with `bun create gridland` to scaffold a new project.",
1173
+ "OpenTUI provides the layout primitives \u2014 flexbox, borders, text styling \u2014 while React handles the component model.",
1174
+ "Yes! Gridland apps are universal \u2014 the same code renders in a terminal emulator and in the browser.",
1175
+ "Check out the docs for examples of interactive components like inputs, selects, and tables."
1176
+ ];
1158
1177
  function LandingApp({ useKeyboard }) {
1159
1178
  const theme = useTheme();
1160
1179
  const { width, height, isNarrow, isTiny, isMobile } = useBreakpoints();
1161
1180
  const [showAbout, setShowAbout] = useState10(false);
1181
+ const [messages, setMessages] = useState10([]);
1182
+ const [chatStatus, setChatStatus] = useState10("ready");
1183
+ const responseIdx = useRef8(0);
1184
+ const handleChatSubmit = useCallback3(({ text }) => {
1185
+ const userMsg = { id: `u-${Date.now()}`, role: "user", content: text };
1186
+ setMessages((prev) => [...prev, userMsg]);
1187
+ setChatStatus("streaming");
1188
+ setTimeout(() => {
1189
+ const response = DEMO_RESPONSES[responseIdx.current % DEMO_RESPONSES.length];
1190
+ responseIdx.current += 1;
1191
+ const assistantMsg = { id: `a-${Date.now()}`, role: "assistant", content: response };
1192
+ setMessages((prev) => [...prev, assistantMsg]);
1193
+ setChatStatus("ready");
1194
+ }, 1200);
1195
+ }, []);
1162
1196
  useKeyboard((event) => {
1163
1197
  if (event.name === "a" && !showAbout) {
1164
1198
  setShowAbout(true);
@@ -1185,61 +1219,25 @@ function LandingApp({ useKeyboard }) {
1185
1219
  const installLinksClearRect = { top: installLinksTop, left: 1, width: width - 2, height: installLinksHeight };
1186
1220
  return /* @__PURE__ */ jsxs19("box", { width: "100%", height: "100%", position: "relative", children: [
1187
1221
  /* @__PURE__ */ jsx26(MatrixBackground, { width, height, clearRect, clearRects: [installLinksClearRect] }),
1188
- /* @__PURE__ */ jsxs19(
1189
- "box",
1190
- {
1191
- position: "absolute",
1192
- top: 0,
1193
- left: 0,
1194
- width,
1195
- height,
1196
- zIndex: 1,
1197
- flexDirection: "column",
1198
- shouldFill: false,
1199
- children: [
1200
- /* @__PURE__ */ jsxs19("box", { flexGrow: 1, flexDirection: "column", paddingTop: 3, paddingLeft: 1, paddingRight: 1, paddingBottom: 1, gap: isMobile ? 0 : 1, shouldFill: false, children: [
1201
- /* @__PURE__ */ jsx26("box", { flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx26(Logo, { compact: isTiny, narrow: isNarrow, mobile: isMobile }) }),
1202
- /* @__PURE__ */ jsxs19("box", { flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: isMobile ? 0 : 1, flexShrink: 0, shouldFill: false, children: [
1203
- /* @__PURE__ */ jsx26(
1204
- "box",
1205
- {
1206
- border: true,
1207
- borderStyle: "rounded",
1208
- borderColor: theme.border,
1209
- paddingX: 1,
1210
- flexDirection: "column",
1211
- flexShrink: 0,
1212
- children: /* @__PURE__ */ jsxs19("text", { children: [
1213
- /* @__PURE__ */ jsx26("span", { style: textStyle({ dim: true }), children: "$ " }),
1214
- /* @__PURE__ */ jsx26("span", { style: textStyle({ bold: true }), children: "bunx " }),
1215
- /* @__PURE__ */ jsx26("span", { style: textStyle({ fg: theme.accent }), children: "@gridland/demo landing" })
1216
- ] })
1217
- }
1218
- ),
1219
- /* @__PURE__ */ jsx26(InstallBox, {}),
1220
- /* @__PURE__ */ jsx26(LinksBox, {})
1221
- ] }),
1222
- /* @__PURE__ */ jsx26(
1223
- "box",
1224
- {
1225
- flexGrow: 1,
1226
- border: true,
1227
- borderStyle: "rounded",
1228
- borderColor: theme.border
1229
- }
1230
- )
1231
- ] }),
1232
- /* @__PURE__ */ jsx26(
1233
- StatusBar,
1234
- {
1235
- items: [
1236
- { key: "a", label: "about" }
1237
- ]
1238
- }
1239
- )
1240
- ]
1241
- }
1242
- )
1222
+ /* @__PURE__ */ jsxs19("box", { position: "absolute", top: 0, left: 0, width, height, zIndex: 1, flexDirection: "column", shouldFill: false, children: [
1223
+ /* @__PURE__ */ jsxs19("box", { flexGrow: 1, flexDirection: "column", paddingTop: 3, paddingLeft: 1, paddingRight: 1, paddingBottom: 1, gap: isMobile ? 0 : 1, shouldFill: false, children: [
1224
+ /* @__PURE__ */ jsx26("box", { flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx26(Logo, { compact: isTiny, narrow: isNarrow, mobile: isMobile }) }),
1225
+ /* @__PURE__ */ jsxs19("box", { flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: isMobile ? 0 : 1, flexShrink: 0, shouldFill: false, children: [
1226
+ /* @__PURE__ */ jsx26("box", { border: true, borderStyle: "rounded", borderColor: theme.border, paddingX: 1, flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsxs19("text", { children: [
1227
+ /* @__PURE__ */ jsx26("span", { style: textStyle({ dim: true }), children: "$ " }),
1228
+ /* @__PURE__ */ jsx26("span", { style: textStyle({ bold: true }), children: "bunx " }),
1229
+ /* @__PURE__ */ jsx26("span", { style: textStyle({ fg: theme.accent }), children: "@gridland/demo landing" })
1230
+ ] }) }),
1231
+ /* @__PURE__ */ jsx26(InstallBox, {}),
1232
+ /* @__PURE__ */ jsx26(LinksBox, {})
1233
+ ] }),
1234
+ /* @__PURE__ */ jsxs19("box", { flexGrow: 1, border: true, borderStyle: "rounded", borderColor: theme.border, flexDirection: "column", overflow: "hidden", children: [
1235
+ /* @__PURE__ */ jsx26("box", { flexGrow: 1, flexDirection: "column", paddingX: 1, overflow: "hidden", children: messages.map((msg) => /* @__PURE__ */ jsx26(Message, { role: msg.role, children: /* @__PURE__ */ jsx26(Message.Content, { children: /* @__PURE__ */ jsx26(Message.Text, { children: msg.content }) }) }, msg.id)) }),
1236
+ /* @__PURE__ */ jsx26("box", { flexShrink: 0, paddingX: 1, paddingBottom: 0, children: /* @__PURE__ */ jsx26(PromptInput, { splaceholder: "Ask about Gridland...", status: chatStatus, onSubmit: handleChatSubmit, useKeyboard }) })
1237
+ ] })
1238
+ ] }),
1239
+ /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "a", label: "about" }] })
1240
+ ] })
1243
1241
  ] });
1244
1242
  }
1245
1243
 
package/dist/run.js CHANGED
@@ -3,7 +3,7 @@ import { createCliRenderer } from "@opentui/core";
3
3
  import { createRoot, useKeyboard as useKeyboard2 } from "@opentui/react";
4
4
 
5
5
  // ../ui/scripts/demo-apps.tsx
6
- import { useState as useState11, useCallback as useCallback3, useRef as useRef8, useEffect as useEffect5 } from "react";
6
+ import { useState as useState11, useCallback as useCallback4, useRef as useRef9, useEffect as useEffect5 } from "react";
7
7
  import { useKeyboard } from "@opentui/react";
8
8
 
9
9
  // ../ui/components/theme/themes.ts
@@ -1077,6 +1077,14 @@ function PromptInputStatusText() {
1077
1077
  if (status !== "error") return null;
1078
1078
  return /* @__PURE__ */ jsx16("text", { children: /* @__PURE__ */ jsx16("span", { style: textStyle({ fg: theme.error }), children: errorText }) });
1079
1079
  }
1080
+ function PromptInputModel() {
1081
+ const { model, theme } = usePromptInput();
1082
+ if (!model) return null;
1083
+ return /* @__PURE__ */ jsxs10("text", { dim: true, color: theme.muted, children: [
1084
+ "model: ",
1085
+ model
1086
+ ] });
1087
+ }
1080
1088
  function PromptInput({
1081
1089
  value: controlledValue,
1082
1090
  defaultValue = "",
@@ -1097,7 +1105,8 @@ function PromptInput({
1097
1105
  getSuggestions: customGetSuggestions,
1098
1106
  maxSuggestions = 5,
1099
1107
  enableHistory = true,
1100
- showDividers = false,
1108
+ model,
1109
+ showDividers = true,
1101
1110
  useKeyboard: useKeyboardProp,
1102
1111
  children
1103
1112
  }) {
@@ -1292,6 +1301,7 @@ function PromptInput({
1292
1301
  sugIdx,
1293
1302
  maxSuggestions,
1294
1303
  errorText,
1304
+ model,
1295
1305
  theme
1296
1306
  };
1297
1307
  if (children) {
@@ -1302,6 +1312,7 @@ function PromptInput({
1302
1312
  /* @__PURE__ */ jsx16(PromptInputSuggestions, {}),
1303
1313
  /* @__PURE__ */ jsx16(PromptInputTextarea, {}),
1304
1314
  /* @__PURE__ */ jsx16(PromptInputStatusText, {}),
1315
+ /* @__PURE__ */ jsx16(PromptInputModel, {}),
1305
1316
  showDividers && /* @__PURE__ */ jsx16(PromptInputDivider, {})
1306
1317
  ] }) });
1307
1318
  }
@@ -1310,6 +1321,7 @@ PromptInput.Suggestions = PromptInputSuggestions;
1310
1321
  PromptInput.Submit = PromptInputSubmit;
1311
1322
  PromptInput.Divider = PromptInputDivider;
1312
1323
  PromptInput.StatusText = PromptInputStatusText;
1324
+ PromptInput.Model = PromptInputModel;
1313
1325
 
1314
1326
  // ../ui/components/chat/chat.tsx
1315
1327
  import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
@@ -1675,13 +1687,110 @@ function useBreakpoints() {
1675
1687
  }
1676
1688
 
1677
1689
  // ../docs/components/landing/landing-app.tsx
1678
- import { useState as useState10 } from "react";
1690
+ import { useCallback as useCallback3, useRef as useRef8, useState as useState10 } from "react";
1691
+
1692
+ // ../docs/components/landing/about-modal.tsx
1693
+ import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
1694
+ function AboutModal({ onClose, useKeyboard: useKeyboard3 }) {
1695
+ const theme = useTheme();
1696
+ return /* @__PURE__ */ jsx21(Modal, { title: "About Gridland", useKeyboard: useKeyboard3, onClose, children: /* @__PURE__ */ jsxs15("box", { paddingX: 1, flexDirection: "column", gap: 1, children: [
1697
+ /* @__PURE__ */ jsx21("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "What is Gridland?" }),
1698
+ /* @__PURE__ */ jsx21("text", { children: "Gridland renders terminal UIs to HTML5 Canvas with React." }),
1699
+ /* @__PURE__ */ jsx21("text", { children: "No xterm.js. No terminal emulator. Just pixels." }),
1700
+ /* @__PURE__ */ jsx21("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "Features" }),
1701
+ /* @__PURE__ */ jsxs15("text", { children: [
1702
+ /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
1703
+ "\u2022",
1704
+ " "
1705
+ ] }),
1706
+ "Canvas-rendered TUI components"
1707
+ ] }),
1708
+ /* @__PURE__ */ jsxs15("text", { children: [
1709
+ /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
1710
+ "\u2022",
1711
+ " "
1712
+ ] }),
1713
+ "React reconciler with JSX"
1714
+ ] }),
1715
+ /* @__PURE__ */ jsxs15("text", { children: [
1716
+ /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
1717
+ "\u2022",
1718
+ " "
1719
+ ] }),
1720
+ "Yoga flexbox layout engine"
1721
+ ] }),
1722
+ /* @__PURE__ */ jsxs15("text", { children: [
1723
+ /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
1724
+ "\u2022",
1725
+ " "
1726
+ ] }),
1727
+ "Keyboard, mouse, and clipboard support"
1728
+ ] }),
1729
+ /* @__PURE__ */ jsxs15("text", { children: [
1730
+ /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
1731
+ "\u2022",
1732
+ " "
1733
+ ] }),
1734
+ "Next.js and Vite plugins"
1735
+ ] }),
1736
+ /* @__PURE__ */ jsx21("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "Tech Stack" }),
1737
+ /* @__PURE__ */ jsx21("text", { children: "React + opentui engine + yoga-layout + HTML5 Canvas" }),
1738
+ /* @__PURE__ */ jsx21("text", { style: textStyle({ dim: true }), children: "Press q to close" })
1739
+ ] }) });
1740
+ }
1741
+
1742
+ // ../docs/components/landing/install-box.tsx
1743
+ import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
1744
+ function InstallBox() {
1745
+ const theme = useTheme();
1746
+ return /* @__PURE__ */ jsx22(
1747
+ "box",
1748
+ {
1749
+ border: true,
1750
+ borderStyle: "rounded",
1751
+ borderColor: theme.border,
1752
+ paddingX: 1,
1753
+ flexDirection: "column",
1754
+ flexShrink: 0,
1755
+ children: /* @__PURE__ */ jsxs16("text", { children: [
1756
+ /* @__PURE__ */ jsx22("span", { style: textStyle({ dim: true }), children: "$ " }),
1757
+ /* @__PURE__ */ jsx22("span", { style: textStyle({ bold: true }), children: "bun create " }),
1758
+ /* @__PURE__ */ jsx22("span", { style: textStyle({ fg: theme.accent }), children: "gridland" })
1759
+ ] })
1760
+ }
1761
+ );
1762
+ }
1763
+
1764
+ // ../docs/components/landing/links-box.tsx
1765
+ import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
1766
+ var UNDERLINE3 = 1 << 3;
1767
+ function LinksBox() {
1768
+ const theme = useTheme();
1769
+ return /* @__PURE__ */ jsx23(
1770
+ "box",
1771
+ {
1772
+ border: true,
1773
+ borderStyle: "rounded",
1774
+ borderColor: theme.border,
1775
+ paddingX: 1,
1776
+ flexDirection: "column",
1777
+ flexShrink: 0,
1778
+ children: /* @__PURE__ */ jsxs17("text", { children: [
1779
+ /* @__PURE__ */ jsx23("span", { children: "\u{1F431}" }),
1780
+ /* @__PURE__ */ jsx23("a", { href: "https://github.com/cjroth/gridland", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " GitHub" }),
1781
+ /* @__PURE__ */ jsx23("span", { children: " " }),
1782
+ /* @__PURE__ */ jsx23("span", { children: "\u{1F4D6}" }),
1783
+ /* @__PURE__ */ jsx23("a", { href: "/docs", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " Docs" })
1784
+ ] })
1785
+ }
1786
+ );
1787
+ }
1679
1788
 
1680
1789
  // ../docs/components/landing/logo.tsx
1681
1790
  import { useState as useState8, useEffect as useEffect3, useRef as useRef6, useMemo as useMemo4 } from "react";
1682
1791
  import figlet from "figlet";
1683
1792
  import ansiShadow from "figlet/importable-fonts/ANSI Shadow.js";
1684
- import { Fragment as Fragment6, jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
1793
+ import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
1685
1794
  figlet.parseFont("ANSI Shadow", ansiShadow);
1686
1795
  function makeArt(text) {
1687
1796
  return figlet.textSync(text, { font: "ANSI Shadow" }).split("\n").filter((l) => l.trimEnd().length > 0).join("\n");
@@ -1714,9 +1823,9 @@ function RevealGradient({ children, revealCol }) {
1714
1823
  const gradientColors = GRADIENTS.instagram;
1715
1824
  const lines = children.split("\n");
1716
1825
  const maxLength = Math.max(...lines.map((l) => l.length));
1717
- if (maxLength === 0) return /* @__PURE__ */ jsx21("text", { children });
1826
+ if (maxLength === 0) return /* @__PURE__ */ jsx24("text", { children });
1718
1827
  const hexColors = useMemo4(() => generateGradient(gradientColors, maxLength), [maxLength]);
1719
- return /* @__PURE__ */ jsx21("box", { position: "relative", width: maxLength, height: lines.length, shouldFill: false, children: lines.map((line, lineIndex) => {
1828
+ return /* @__PURE__ */ jsx24("box", { position: "relative", width: maxLength, height: lines.length, shouldFill: false, children: lines.map((line, lineIndex) => {
1720
1829
  const runs = [];
1721
1830
  let current = null;
1722
1831
  for (let i = 0; i < line.length; i++) {
@@ -1736,14 +1845,14 @@ function RevealGradient({ children, revealCol }) {
1736
1845
  }
1737
1846
  }
1738
1847
  if (current) runs.push(current);
1739
- return runs.map((run, runIndex) => /* @__PURE__ */ jsx21(
1848
+ return runs.map((run, runIndex) => /* @__PURE__ */ jsx24(
1740
1849
  "box",
1741
1850
  {
1742
1851
  position: "absolute",
1743
1852
  top: lineIndex,
1744
1853
  left: run.start,
1745
1854
  shouldFill: false,
1746
- children: /* @__PURE__ */ jsx21("text", { shouldFill: false, children: run.chars.map((char, ci) => /* @__PURE__ */ jsx21(
1855
+ children: /* @__PURE__ */ jsx24("text", { shouldFill: false, children: run.chars.map((char, ci) => /* @__PURE__ */ jsx24(
1747
1856
  "span",
1748
1857
  {
1749
1858
  style: { fg: hexColors[run.start + ci] },
@@ -1765,20 +1874,20 @@ function Logo({ compact, narrow, mobile }) {
1765
1874
  const maxWidth = compact ? 8 : narrow ? 40 : 62;
1766
1875
  const revealCol = Math.round(revealProgress * (maxWidth + 4)) - 2;
1767
1876
  const taglineOpacity = Math.max(0, Math.min(1, (progress - 0.7) / 0.3));
1768
- const subtitle = /* @__PURE__ */ jsxs15(Fragment6, { children: [
1769
- /* @__PURE__ */ jsx21("text", { children: " " }),
1770
- /* @__PURE__ */ jsx21("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs15("text", { style: textStyle({ fg: "#d4b0e8" }), opacity: taglineOpacity, wrapMode: "word", textAlign: "center", width: "100%", shouldFill: false, children: [
1877
+ const subtitle = /* @__PURE__ */ jsxs18(Fragment6, { children: [
1878
+ /* @__PURE__ */ jsx24("text", { children: " " }),
1879
+ /* @__PURE__ */ jsx24("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs18("text", { style: textStyle({ fg: "#d4b0e8" }), opacity: taglineOpacity, wrapMode: "word", textAlign: "center", width: "100%", shouldFill: false, children: [
1771
1880
  "A framework for building terminal apps, built on ",
1772
- /* @__PURE__ */ jsx21("a", { href: "https://opentui.com", style: { attributes: 72, fg: "#d4b0e8" }, children: "OpenTUI" }),
1881
+ /* @__PURE__ */ jsx24("a", { href: "https://opentui.com", style: { attributes: 72, fg: "#d4b0e8" }, children: "OpenTUI" }),
1773
1882
  " + React." + (mobile ? " " : "\n") + "(Gridland apps, like this website, work in the browser and terminal.)"
1774
1883
  ] }) })
1775
1884
  ] });
1776
1885
  if (!isBrowser) {
1777
1886
  const art = compact ? "gridland" : narrow ? gridArt + "\n" + landArt : fullArt;
1778
- return /* @__PURE__ */ jsxs15("box", { flexDirection: "column", flexShrink: 0, width: "100%", alignItems: "center", children: [
1779
- /* @__PURE__ */ jsx21(Gradient, { name: "instagram", children: art }),
1780
- /* @__PURE__ */ jsx21("text", { children: " " }),
1781
- /* @__PURE__ */ jsx21("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs15("text", { style: textStyle({ fg: "#d4b0e8" }), shouldFill: false, children: [
1887
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexShrink: 0, width: "100%", alignItems: "center", children: [
1888
+ /* @__PURE__ */ jsx24(Gradient, { name: "instagram", children: art }),
1889
+ /* @__PURE__ */ jsx24("text", { children: " " }),
1890
+ /* @__PURE__ */ jsx24("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs18("text", { style: textStyle({ fg: "#d4b0e8" }), shouldFill: false, children: [
1782
1891
  "A framework for building terminal apps, built on OpenTUI + React.",
1783
1892
  "\n",
1784
1893
  "(Gridland apps, like this website, work in the browser and terminal.)"
@@ -1786,73 +1895,26 @@ function Logo({ compact, narrow, mobile }) {
1786
1895
  ] });
1787
1896
  }
1788
1897
  if (compact) {
1789
- return /* @__PURE__ */ jsxs15("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1790
- /* @__PURE__ */ jsx21("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx21("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx21(RevealGradient, { revealCol, children: "gridland" }) }) }),
1898
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1899
+ /* @__PURE__ */ jsx24("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx24("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx24(RevealGradient, { revealCol, children: "gridland" }) }) }),
1791
1900
  subtitle
1792
1901
  ] });
1793
1902
  }
1794
1903
  if (narrow) {
1795
- return /* @__PURE__ */ jsxs15("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1796
- /* @__PURE__ */ jsx21("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsxs15("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: [
1797
- /* @__PURE__ */ jsx21(RevealGradient, { revealCol, children: gridArt }),
1798
- /* @__PURE__ */ jsx21(RevealGradient, { revealCol, children: landArt })
1904
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1905
+ /* @__PURE__ */ jsx24("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsxs18("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: [
1906
+ /* @__PURE__ */ jsx24(RevealGradient, { revealCol, children: gridArt }),
1907
+ /* @__PURE__ */ jsx24(RevealGradient, { revealCol, children: landArt })
1799
1908
  ] }) }),
1800
1909
  subtitle
1801
1910
  ] });
1802
1911
  }
1803
- return /* @__PURE__ */ jsxs15("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1804
- /* @__PURE__ */ jsx21("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx21("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx21(RevealGradient, { revealCol, children: fullArt }) }) }),
1912
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1913
+ /* @__PURE__ */ jsx24("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx24("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx24(RevealGradient, { revealCol, children: fullArt }) }) }),
1805
1914
  subtitle
1806
1915
  ] });
1807
1916
  }
1808
1917
 
1809
- // ../docs/components/landing/install-box.tsx
1810
- import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
1811
- function InstallBox() {
1812
- const theme = useTheme();
1813
- return /* @__PURE__ */ jsx22(
1814
- "box",
1815
- {
1816
- border: true,
1817
- borderStyle: "rounded",
1818
- borderColor: theme.border,
1819
- paddingX: 1,
1820
- flexDirection: "column",
1821
- flexShrink: 0,
1822
- children: /* @__PURE__ */ jsxs16("text", { children: [
1823
- /* @__PURE__ */ jsx22("span", { style: textStyle({ dim: true }), children: "$ " }),
1824
- /* @__PURE__ */ jsx22("span", { style: textStyle({ bold: true }), children: "bun create " }),
1825
- /* @__PURE__ */ jsx22("span", { style: textStyle({ fg: theme.accent }), children: "gridland" })
1826
- ] })
1827
- }
1828
- );
1829
- }
1830
-
1831
- // ../docs/components/landing/links-box.tsx
1832
- import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
1833
- var UNDERLINE3 = 1 << 3;
1834
- function LinksBox() {
1835
- const theme = useTheme();
1836
- return /* @__PURE__ */ jsx23(
1837
- "box",
1838
- {
1839
- border: true,
1840
- borderStyle: "rounded",
1841
- borderColor: theme.border,
1842
- paddingX: 1,
1843
- flexDirection: "column",
1844
- flexShrink: 0,
1845
- children: /* @__PURE__ */ jsxs17("text", { children: [
1846
- /* @__PURE__ */ jsx23("span", { children: "\u{1F431}" }),
1847
- /* @__PURE__ */ jsx23("a", { href: "https://github.com/cjroth/gridland", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " GitHub" }),
1848
- /* @__PURE__ */ jsx23("span", { children: " " }),
1849
- /* @__PURE__ */ jsx23("span", { children: "\u{1F4D6}" }),
1850
- /* @__PURE__ */ jsx23("a", { href: "/docs", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " Docs" })
1851
- ] })
1852
- }
1853
- );
1854
- }
1855
-
1856
1918
  // ../docs/components/landing/matrix-background.tsx
1857
1919
  import { useMemo as useMemo5 } from "react";
1858
1920
 
@@ -1936,7 +1998,7 @@ function useMatrix(width, height) {
1936
1998
  }
1937
1999
 
1938
2000
  // ../docs/components/landing/matrix-background.tsx
1939
- import { jsx as jsx24 } from "react/jsx-runtime";
2001
+ import { jsx as jsx25 } from "react/jsx-runtime";
1940
2002
  var MUTE_LEVELS = [0.12, 0.18, 0.24, 0.3, 0.38];
1941
2003
  var BG = hexToRgb("#1a1a2e");
1942
2004
  function buildMutedColors(baseHex) {
@@ -1963,15 +2025,15 @@ function MatrixBackground({ width, height, clearRect, clearRects }) {
1963
2025
  () => columnColors.map(buildMutedColors),
1964
2026
  [columnColors]
1965
2027
  );
1966
- return /* @__PURE__ */ jsx24("box", { flexDirection: "column", children: grid.map((row, y) => /* @__PURE__ */ jsx24("text", { children: row.map((cell, x) => {
2028
+ return /* @__PURE__ */ jsx25("box", { flexDirection: "column", children: grid.map((row, y) => /* @__PURE__ */ jsx25("text", { children: row.map((cell, x) => {
1967
2029
  const inClearRect = clearRect && y >= clearRect.top && y < clearRect.top + clearRect.height && x >= clearRect.left && x < clearRect.left + clearRect.width || clearRects && clearRects.some(
1968
2030
  (r) => y >= r.top && y < r.top + r.height && x >= r.left && x < r.left + r.width
1969
2031
  );
1970
2032
  const mutedColors = columnMutedColors[x];
1971
2033
  if (cell === " " || inClearRect || !mutedColors) {
1972
- return /* @__PURE__ */ jsx24("span", { children: " " }, x);
2034
+ return /* @__PURE__ */ jsx25("span", { children: " " }, x);
1973
2035
  }
1974
- return /* @__PURE__ */ jsx24(
2036
+ return /* @__PURE__ */ jsx25(
1975
2037
  "span",
1976
2038
  {
1977
2039
  style: {
@@ -1984,62 +2046,34 @@ function MatrixBackground({ width, height, clearRect, clearRects }) {
1984
2046
  }) }, y)) });
1985
2047
  }
1986
2048
 
1987
- // ../docs/components/landing/about-modal.tsx
1988
- import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
1989
- function AboutModal({ onClose, useKeyboard: useKeyboard3 }) {
1990
- const theme = useTheme();
1991
- return /* @__PURE__ */ jsx25(Modal, { title: "About Gridland", useKeyboard: useKeyboard3, onClose, children: /* @__PURE__ */ jsxs18("box", { paddingX: 1, flexDirection: "column", gap: 1, children: [
1992
- /* @__PURE__ */ jsx25("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "What is Gridland?" }),
1993
- /* @__PURE__ */ jsx25("text", { children: "Gridland renders terminal UIs to HTML5 Canvas with React." }),
1994
- /* @__PURE__ */ jsx25("text", { children: "No xterm.js. No terminal emulator. Just pixels." }),
1995
- /* @__PURE__ */ jsx25("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "Features" }),
1996
- /* @__PURE__ */ jsxs18("text", { children: [
1997
- /* @__PURE__ */ jsxs18("span", { style: textStyle({ dim: true }), children: [
1998
- "\u2022",
1999
- " "
2000
- ] }),
2001
- "Canvas-rendered TUI components"
2002
- ] }),
2003
- /* @__PURE__ */ jsxs18("text", { children: [
2004
- /* @__PURE__ */ jsxs18("span", { style: textStyle({ dim: true }), children: [
2005
- "\u2022",
2006
- " "
2007
- ] }),
2008
- "React reconciler with JSX"
2009
- ] }),
2010
- /* @__PURE__ */ jsxs18("text", { children: [
2011
- /* @__PURE__ */ jsxs18("span", { style: textStyle({ dim: true }), children: [
2012
- "\u2022",
2013
- " "
2014
- ] }),
2015
- "Yoga flexbox layout engine"
2016
- ] }),
2017
- /* @__PURE__ */ jsxs18("text", { children: [
2018
- /* @__PURE__ */ jsxs18("span", { style: textStyle({ dim: true }), children: [
2019
- "\u2022",
2020
- " "
2021
- ] }),
2022
- "Keyboard, mouse, and clipboard support"
2023
- ] }),
2024
- /* @__PURE__ */ jsxs18("text", { children: [
2025
- /* @__PURE__ */ jsxs18("span", { style: textStyle({ dim: true }), children: [
2026
- "\u2022",
2027
- " "
2028
- ] }),
2029
- "Next.js and Vite plugins"
2030
- ] }),
2031
- /* @__PURE__ */ jsx25("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "Tech Stack" }),
2032
- /* @__PURE__ */ jsx25("text", { children: "React + opentui engine + yoga-layout + HTML5 Canvas" }),
2033
- /* @__PURE__ */ jsx25("text", { style: textStyle({ dim: true }), children: "Press q to close" })
2034
- ] }) });
2035
- }
2036
-
2037
2049
  // ../docs/components/landing/landing-app.tsx
2038
2050
  import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
2051
+ var DEMO_RESPONSES = [
2052
+ "Gridland is a framework for building terminal apps with React. It works in both the browser and terminal!",
2053
+ "You can get started with `bun create gridland` to scaffold a new project.",
2054
+ "OpenTUI provides the layout primitives \u2014 flexbox, borders, text styling \u2014 while React handles the component model.",
2055
+ "Yes! Gridland apps are universal \u2014 the same code renders in a terminal emulator and in the browser.",
2056
+ "Check out the docs for examples of interactive components like inputs, selects, and tables."
2057
+ ];
2039
2058
  function LandingApp({ useKeyboard: useKeyboard3 }) {
2040
2059
  const theme = useTheme();
2041
2060
  const { width, height, isNarrow, isTiny, isMobile } = useBreakpoints();
2042
2061
  const [showAbout, setShowAbout] = useState10(false);
2062
+ const [messages, setMessages] = useState10([]);
2063
+ const [chatStatus, setChatStatus] = useState10("ready");
2064
+ const responseIdx = useRef8(0);
2065
+ const handleChatSubmit = useCallback3(({ text }) => {
2066
+ const userMsg = { id: `u-${Date.now()}`, role: "user", content: text };
2067
+ setMessages((prev) => [...prev, userMsg]);
2068
+ setChatStatus("streaming");
2069
+ setTimeout(() => {
2070
+ const response = DEMO_RESPONSES[responseIdx.current % DEMO_RESPONSES.length];
2071
+ responseIdx.current += 1;
2072
+ const assistantMsg = { id: `a-${Date.now()}`, role: "assistant", content: response };
2073
+ setMessages((prev) => [...prev, assistantMsg]);
2074
+ setChatStatus("ready");
2075
+ }, 1200);
2076
+ }, []);
2043
2077
  useKeyboard3((event) => {
2044
2078
  if (event.name === "a" && !showAbout) {
2045
2079
  setShowAbout(true);
@@ -2066,61 +2100,25 @@ function LandingApp({ useKeyboard: useKeyboard3 }) {
2066
2100
  const installLinksClearRect = { top: installLinksTop, left: 1, width: width - 2, height: installLinksHeight };
2067
2101
  return /* @__PURE__ */ jsxs19("box", { width: "100%", height: "100%", position: "relative", children: [
2068
2102
  /* @__PURE__ */ jsx26(MatrixBackground, { width, height, clearRect, clearRects: [installLinksClearRect] }),
2069
- /* @__PURE__ */ jsxs19(
2070
- "box",
2071
- {
2072
- position: "absolute",
2073
- top: 0,
2074
- left: 0,
2075
- width,
2076
- height,
2077
- zIndex: 1,
2078
- flexDirection: "column",
2079
- shouldFill: false,
2080
- children: [
2081
- /* @__PURE__ */ jsxs19("box", { flexGrow: 1, flexDirection: "column", paddingTop: 3, paddingLeft: 1, paddingRight: 1, paddingBottom: 1, gap: isMobile ? 0 : 1, shouldFill: false, children: [
2082
- /* @__PURE__ */ jsx26("box", { flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx26(Logo, { compact: isTiny, narrow: isNarrow, mobile: isMobile }) }),
2083
- /* @__PURE__ */ jsxs19("box", { flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: isMobile ? 0 : 1, flexShrink: 0, shouldFill: false, children: [
2084
- /* @__PURE__ */ jsx26(
2085
- "box",
2086
- {
2087
- border: true,
2088
- borderStyle: "rounded",
2089
- borderColor: theme.border,
2090
- paddingX: 1,
2091
- flexDirection: "column",
2092
- flexShrink: 0,
2093
- children: /* @__PURE__ */ jsxs19("text", { children: [
2094
- /* @__PURE__ */ jsx26("span", { style: textStyle({ dim: true }), children: "$ " }),
2095
- /* @__PURE__ */ jsx26("span", { style: textStyle({ bold: true }), children: "bunx " }),
2096
- /* @__PURE__ */ jsx26("span", { style: textStyle({ fg: theme.accent }), children: "@gridland/demo landing" })
2097
- ] })
2098
- }
2099
- ),
2100
- /* @__PURE__ */ jsx26(InstallBox, {}),
2101
- /* @__PURE__ */ jsx26(LinksBox, {})
2102
- ] }),
2103
- /* @__PURE__ */ jsx26(
2104
- "box",
2105
- {
2106
- flexGrow: 1,
2107
- border: true,
2108
- borderStyle: "rounded",
2109
- borderColor: theme.border
2110
- }
2111
- )
2112
- ] }),
2113
- /* @__PURE__ */ jsx26(
2114
- StatusBar,
2115
- {
2116
- items: [
2117
- { key: "a", label: "about" }
2118
- ]
2119
- }
2120
- )
2121
- ]
2122
- }
2123
- )
2103
+ /* @__PURE__ */ jsxs19("box", { position: "absolute", top: 0, left: 0, width, height, zIndex: 1, flexDirection: "column", shouldFill: false, children: [
2104
+ /* @__PURE__ */ jsxs19("box", { flexGrow: 1, flexDirection: "column", paddingTop: 3, paddingLeft: 1, paddingRight: 1, paddingBottom: 1, gap: isMobile ? 0 : 1, shouldFill: false, children: [
2105
+ /* @__PURE__ */ jsx26("box", { flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx26(Logo, { compact: isTiny, narrow: isNarrow, mobile: isMobile }) }),
2106
+ /* @__PURE__ */ jsxs19("box", { flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: isMobile ? 0 : 1, flexShrink: 0, shouldFill: false, children: [
2107
+ /* @__PURE__ */ jsx26("box", { border: true, borderStyle: "rounded", borderColor: theme.border, paddingX: 1, flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsxs19("text", { children: [
2108
+ /* @__PURE__ */ jsx26("span", { style: textStyle({ dim: true }), children: "$ " }),
2109
+ /* @__PURE__ */ jsx26("span", { style: textStyle({ bold: true }), children: "bunx " }),
2110
+ /* @__PURE__ */ jsx26("span", { style: textStyle({ fg: theme.accent }), children: "@gridland/demo landing" })
2111
+ ] }) }),
2112
+ /* @__PURE__ */ jsx26(InstallBox, {}),
2113
+ /* @__PURE__ */ jsx26(LinksBox, {})
2114
+ ] }),
2115
+ /* @__PURE__ */ jsxs19("box", { flexGrow: 1, border: true, borderStyle: "rounded", borderColor: theme.border, flexDirection: "column", overflow: "hidden", children: [
2116
+ /* @__PURE__ */ jsx26("box", { flexGrow: 1, flexDirection: "column", paddingX: 1, overflow: "hidden", children: messages.map((msg) => /* @__PURE__ */ jsx26(Message, { role: msg.role, children: /* @__PURE__ */ jsx26(Message.Content, { children: /* @__PURE__ */ jsx26(Message.Text, { children: msg.content }) }) }, msg.id)) }),
2117
+ /* @__PURE__ */ jsx26("box", { flexShrink: 0, paddingX: 1, paddingBottom: 0, children: /* @__PURE__ */ jsx26(PromptInput, { splaceholder: "Ask about Gridland...", status: chatStatus, onSubmit: handleChatSubmit, useKeyboard: useKeyboard3 }) })
2118
+ ] })
2119
+ ] }),
2120
+ /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "a", label: "about" }] })
2121
+ ] })
2124
2122
  ] });
2125
2123
  }
2126
2124
 
@@ -2485,7 +2483,7 @@ function TimelineApp() {
2485
2483
  const [expanded, setExpanded] = useState11(true);
2486
2484
  const [phase, setPhase] = useState11("running");
2487
2485
  const [stepIndex, setStepIndex] = useState11(0);
2488
- const timerRef = useRef8(null);
2486
+ const timerRef = useRef9(null);
2489
2487
  useKeyboard((event) => {
2490
2488
  if (event.name === "E" && event.ctrl && event.shift) setExpanded((v) => !v);
2491
2489
  if (event.name === "r") timelineRestart();
@@ -2552,7 +2550,7 @@ function MessageApp() {
2552
2550
  const [phase, setPhase] = useState11("idle");
2553
2551
  const [stepIndex, setStepIndex] = useState11(0);
2554
2552
  const [streamedText, setStreamedText] = useState11("");
2555
- const timerRef = useRef8(null);
2553
+ const timerRef = useRef9(null);
2556
2554
  useKeyboard((event) => {
2557
2555
  if (event.name === "E" && event.ctrl && event.shift) setExpanded((v) => !v);
2558
2556
  if (event.name === "r") bubbleRestart();
@@ -2644,8 +2642,8 @@ function ChatApp() {
2644
2642
  const [isLoading, setIsLoading] = useState11(false);
2645
2643
  const [streamingText, setStreamingText] = useState11("");
2646
2644
  const [activeToolCalls, setActiveToolCalls] = useState11([]);
2647
- const intervalRef = useRef8(null);
2648
- const handleSend = useCallback3((text) => {
2645
+ const intervalRef = useRef9(null);
2646
+ const handleSend = useCallback4((text) => {
2649
2647
  const userMsg = { id: String(chatNextId++), role: "user", content: text };
2650
2648
  setMessages((prev) => [...prev, userMsg]);
2651
2649
  setIsLoading(true);
@@ -2676,7 +2674,7 @@ function ChatApp() {
2676
2674
  }, 50);
2677
2675
  }, 1400);
2678
2676
  }, []);
2679
- const handleCancel = useCallback3(() => {
2677
+ const handleCancel = useCallback4(() => {
2680
2678
  if (intervalRef.current) clearInterval(intervalRef.current);
2681
2679
  setIsLoading(false);
2682
2680
  setStreamingText("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gridland/demo",
3
- "version": "0.2.20",
3
+ "version": "0.2.22",
4
4
  "type": "module",
5
5
  "description": "Run gridland component demos from anywhere",
6
6
  "exports": {
@@ -20,6 +20,7 @@
20
20
  "test": "bun test"
21
21
  },
22
22
  "dependencies": {
23
+ "@gridland/core": "^0.2.21",
23
24
  "@opentui/core": "^0.1.86",
24
25
  "@opentui/react": "^0.1.86",
25
26
  "figlet": "^1.10.0",