@grackle-ai/web-components 0.108.3 → 0.110.1

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 (30) hide show
  1. package/.rush/temp/3629cc83b3804ac8bcea27905e83162210fb5dd6.tar.log +240 -0
  2. package/.rush/temp/{28d8c029b5d0c4a740412c3ca7a7aa456ad48c1b.untar.log → 3629cc83b3804ac8bcea27905e83162210fb5dd6.untar.log} +2 -2
  3. package/.rush/temp/chunked-rush-logs/web-components._phase_build.chunks.jsonl +18 -0
  4. package/.rush/temp/chunked-rush-logs/web-components._phase_test.chunks.jsonl +121 -0
  5. package/.rush/temp/f2b8b611fc00c7b912256986db4cc966d6560387.tar.log +12 -0
  6. package/.rush/temp/{9f397c070a4229568e12d273f46cf0cda84b9bd2.untar.log → f2b8b611fc00c7b912256986db4cc966d6560387.untar.log} +2 -2
  7. package/.rush/temp/operation/_phase_build/all.log +5 -5
  8. package/.rush/temp/operation/_phase_build/log-chunks.jsonl +5 -5
  9. package/.rush/temp/operation/_phase_build/state.json +1 -1
  10. package/.rush/temp/operation/_phase_test/all.log +21 -21
  11. package/.rush/temp/operation/_phase_test/log-chunks.jsonl +21 -21
  12. package/.rush/temp/operation/_phase_test/state.json +1 -1
  13. package/README.md +95 -0
  14. package/dist/index.css +1 -1
  15. package/dist/index.js +5826 -5732
  16. package/package.json +2 -2
  17. package/rush-logs/web-components._phase_build.cache.log +1 -1
  18. package/rush-logs/web-components._phase_build.log +18 -0
  19. package/rush-logs/web-components._phase_test.cache.log +1 -1
  20. package/rush-logs/web-components._phase_test.log +121 -0
  21. package/src/components/layout/AppNav.module.scss +6 -0
  22. package/src/components/layout/AppNav.stories.tsx +47 -0
  23. package/src/components/layout/AppNav.tsx +24 -10
  24. package/src/components/panels/EnvironmentEditPanel.stories.tsx +87 -1
  25. package/src/components/panels/EnvironmentEditPanel.tsx +143 -34
  26. package/src/context/GrackleContextTypes.ts +3 -1
  27. package/src/hooks/types.ts +21 -0
  28. package/src/index.ts +2 -2
  29. package/src/mocks/MockGrackleProvider.tsx +7 -0
  30. package/temp/build/lint/_eslint-5eVG3S6w.json +810 -0
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback, type JSX } from "react";
2
2
  import type { ToastVariant } from "../../context/ToastContext.js";
3
- import type { Environment, Codespace, GitHubAccountData } from "../../hooks/types.js";
3
+ import type { Environment, Codespace, DockerContainer, GitHubAccountData } from "../../hooks/types.js";
4
4
  import { ENVIRONMENTS_URL, environmentUrl, useAppNavigate } from "../../utils/navigation.js";
5
5
  import { EditableTextField } from "../editable/EditableTextField.js";
6
6
  import styles from "./EnvironmentEditPanel.module.scss";
@@ -35,6 +35,12 @@ interface Props {
35
35
  codespaceCreating: boolean;
36
36
  /** Callback to create a new codespace. */
37
37
  onCreateCodespace: (repo: string, machine?: string) => void;
38
+ /** Callback to list running Docker containers available to attach to. */
39
+ onListDockerContainers: () => void;
40
+ /** Running Docker containers available to attach to (docker attach mode). */
41
+ dockerContainers: DockerContainer[];
42
+ /** Non-fatal error from listing Docker containers (e.g. docker CLI unavailable). */
43
+ dockerContainersError: string;
38
44
  /** Display a toast notification. */
39
45
  onShowToast?: (message: string, variant?: ToastVariant) => void;
40
46
  }
@@ -202,7 +208,7 @@ function CodespacePicker({ codespaceName, onCodespaceNameChange, envName, onEnvN
202
208
  * - edit: pre-populated form; uses click-to-edit fields that auto-save via
203
209
  * updateEnvironment.
204
210
  */
205
- export function EnvironmentEditPanel({ mode, environmentId, environments, githubAccounts, onAddEnvironment, onUpdateEnvironment, onListCodespaces, codespaces, codespaceError, codespaceListError, codespaceCreating, onCreateCodespace, onShowToast }: Props): JSX.Element {
211
+ export function EnvironmentEditPanel({ mode, environmentId, environments, githubAccounts, onAddEnvironment, onUpdateEnvironment, onListCodespaces, codespaces, codespaceError, codespaceListError, codespaceCreating, onCreateCodespace, onListDockerContainers, dockerContainers, dockerContainersError, onShowToast }: Props): JSX.Element {
206
212
  const navigate = useAppNavigate();
207
213
 
208
214
  const isEdit = mode === "edit";
@@ -222,6 +228,9 @@ export function EnvironmentEditPanel({ mode, environmentId, environments, github
222
228
  const [repo, setRepo] = useState("");
223
229
  const [codespaceName, setCodespaceName] = useState("");
224
230
  const [githubAccountId, setGithubAccountId] = useState("");
231
+ // Docker: "create" a new container vs "attach" to an existing one (issue #1223).
232
+ const [dockerMode, setDockerMode] = useState<"create" | "attach">("create");
233
+ const [attachContainer, setAttachContainer] = useState("");
225
234
 
226
235
  // ─── Edit mode state ───────────────────────────────
227
236
 
@@ -257,17 +266,24 @@ export function EnvironmentEditPanel({ mode, environmentId, environments, github
257
266
  config.identityFile = identityFile.trim();
258
267
  }
259
268
  } else if (adapterType === "docker") {
260
- if (image.trim()) {
261
- config.image = image.trim();
262
- }
263
- if (repo.trim()) {
264
- config.repo = repo.trim();
269
+ if (dockerMode === "attach") {
270
+ // Attach mode: target an existing container; image/repo are ignored.
271
+ if (attachContainer.trim()) {
272
+ config.attach = attachContainer.trim();
273
+ }
274
+ } else {
275
+ if (image.trim()) {
276
+ config.image = image.trim();
277
+ }
278
+ if (repo.trim()) {
279
+ config.repo = repo.trim();
280
+ }
265
281
  }
266
282
  } else if (adapterType === "codespace") {
267
283
  config.codespaceName = codespaceName.trim();
268
284
  }
269
285
  return config;
270
- }, [adapterType, host, port, user, identityFile, image, repo, codespaceName]);
286
+ }, [adapterType, host, port, user, identityFile, image, repo, codespaceName, dockerMode, attachContainer]);
271
287
 
272
288
  const isCreateValid = (): boolean => {
273
289
  if (!envName.trim()) {
@@ -279,6 +295,9 @@ export function EnvironmentEditPanel({ mode, environmentId, environments, github
279
295
  if (adapterType === "codespace" && !codespaceName.trim()) {
280
296
  return false;
281
297
  }
298
+ if (adapterType === "docker" && dockerMode === "attach" && !attachContainer.trim()) {
299
+ return false;
300
+ }
282
301
  if ((adapterType === "local" || adapterType === "ssh") && !isPortValid(port)) {
283
302
  return false;
284
303
  }
@@ -533,7 +552,25 @@ export function EnvironmentEditPanel({ mode, environmentId, environments, github
533
552
  </>
534
553
  )}
535
554
 
536
- {existingEnv.adapterType === "docker" && (
555
+ {existingEnv.adapterType === "docker" && config.attach !== undefined && (
556
+ <div className={styles.section}>
557
+ <label className={styles.label}>Attach (container)</label>
558
+ <EditableTextField
559
+ value={String(config.attach ?? "")}
560
+ onSave={(v) => saveConfigField("attach", v)}
561
+ validate={(v) => v.trim() === "" ? "Container name is required" : undefined}
562
+ mode="edit"
563
+ fieldId="attach"
564
+ activeFieldId={activeFieldId}
565
+ onActivate={setActiveFieldId}
566
+ placeholder="container name or ID"
567
+ ariaLabel="Attach container"
568
+ data-testid="env-edit-attach"
569
+ />
570
+ </div>
571
+ )}
572
+
573
+ {existingEnv.adapterType === "docker" && config.attach === undefined && (
537
574
  <>
538
575
  <div className={styles.section}>
539
576
  <label className={styles.label}>Image</label>
@@ -792,33 +829,105 @@ export function EnvironmentEditPanel({ mode, environmentId, environments, github
792
829
  {adapterType === "docker" && (
793
830
  <>
794
831
  <div className={styles.section}>
795
- <label className={styles.label} htmlFor="env-create-image">
796
- Image
832
+ <label className={styles.label} htmlFor="env-docker-mode">
833
+ Source
797
834
  </label>
798
- <input
799
- id="env-create-image"
800
- type="text"
801
- value={image}
802
- onChange={(e) => setImage(e.target.value)}
803
- placeholder="Image (optional)..."
804
- className={styles.fieldInput}
805
- data-testid="env-create-image"
806
- />
807
- </div>
808
- <div className={styles.section}>
809
- <label className={styles.label} htmlFor="env-create-repo">
810
- Repo
811
- </label>
812
- <input
813
- id="env-create-repo"
814
- type="text"
815
- value={repo}
816
- onChange={(e) => setRepo(e.target.value)}
817
- placeholder="Repo (optional)..."
818
- className={styles.fieldInput}
819
- data-testid="env-create-repo"
820
- />
835
+ <select
836
+ id="env-docker-mode"
837
+ value={dockerMode}
838
+ onChange={(e) => {
839
+ const next = e.target.value as "create" | "attach";
840
+ setDockerMode(next);
841
+ if (next === "attach") {
842
+ onListDockerContainers();
843
+ }
844
+ }}
845
+ className={styles.adapterSelect}
846
+ data-testid="env-docker-mode"
847
+ >
848
+ <option value="create">Create new container</option>
849
+ <option value="attach">Attach to existing container</option>
850
+ </select>
821
851
  </div>
852
+
853
+ {dockerMode === "create" ? (
854
+ <>
855
+ <div className={styles.section}>
856
+ <label className={styles.label} htmlFor="env-create-image">
857
+ Image
858
+ </label>
859
+ <input
860
+ id="env-create-image"
861
+ type="text"
862
+ value={image}
863
+ onChange={(e) => setImage(e.target.value)}
864
+ placeholder="Image (optional)..."
865
+ className={styles.fieldInput}
866
+ data-testid="env-create-image"
867
+ />
868
+ </div>
869
+ <div className={styles.section}>
870
+ <label className={styles.label} htmlFor="env-create-repo">
871
+ Repo
872
+ </label>
873
+ <input
874
+ id="env-create-repo"
875
+ type="text"
876
+ value={repo}
877
+ onChange={(e) => setRepo(e.target.value)}
878
+ placeholder="Repo (optional)..."
879
+ className={styles.fieldInput}
880
+ data-testid="env-create-repo"
881
+ />
882
+ </div>
883
+ </>
884
+ ) : (
885
+ <div className={styles.section}>
886
+ <label className={styles.label}>Container</label>
887
+ {!dockerContainersError && dockerContainers.length > 0 && (
888
+ <select
889
+ value={attachContainer}
890
+ onChange={(e) => {
891
+ setAttachContainer(e.target.value);
892
+ if (e.target.value && !envName.trim()) {
893
+ setEnvName(e.target.value);
894
+ }
895
+ }}
896
+ className={styles.adapterSelect}
897
+ data-testid="env-docker-container-select"
898
+ >
899
+ <option value="">Select a container...</option>
900
+ {dockerContainers.map((c) => (
901
+ <option key={c.id} value={c.name}>
902
+ {c.name} ({c.image}) {c.status}
903
+ </option>
904
+ ))}
905
+ </select>
906
+ )}
907
+ {/* Manual entry fallback: shown on listing error OR when no running
908
+ containers were found, so the user is never stuck with an empty picker. */}
909
+ {(dockerContainersError || dockerContainers.length === 0) && (
910
+ <>
911
+ {dockerContainersError
912
+ ? <span className={styles.errorHint}>{dockerContainersError}</span>
913
+ : <span className={styles.creatingHint}>No running containers found.</span>}
914
+ <input
915
+ type="text"
916
+ value={attachContainer}
917
+ onChange={(e) => {
918
+ setAttachContainer(e.target.value);
919
+ if (e.target.value && !envName.trim()) {
920
+ setEnvName(e.target.value);
921
+ }
922
+ }}
923
+ placeholder="Enter container name/ID..."
924
+ className={styles.fieldInput}
925
+ data-testid="env-docker-container-manual"
926
+ />
927
+ </>
928
+ )}
929
+ </div>
930
+ )}
822
931
  </>
823
932
  )}
824
933
 
@@ -10,7 +10,7 @@ import type {
10
10
  UsageStats, UseKnowledgeResult,
11
11
  UseEnvironmentsResult, UseSessionsResult, UseWorkspacesResult,
12
12
  UseTasksResult, UseFindingsResult, UseTokensResult,
13
- UseCredentialsResult, UseCodespacesResult, UsePersonasResult,
13
+ UseCredentialsResult, UseCodespacesResult, UseDockerContainersResult, UsePersonasResult,
14
14
  UsePluginsResult,
15
15
  UseSchedulesResult,
16
16
  UseStreamsResult,
@@ -38,6 +38,8 @@ export interface UseGrackleSocketResult {
38
38
  credentials: Omit<UseCredentialsResult, "handleEvent" | "loadCredentials">;
39
39
  /** GitHub Codespace state and actions. */
40
40
  codespaces: UseCodespacesResult;
41
+ /** Docker container discovery (attach mode) state and actions. */
42
+ dockerContainers: UseDockerContainersResult;
41
43
  /** Persona state and actions. */
42
44
  personas: Omit<UsePersonasResult, "handleEvent" | "loadPersonas">;
43
45
  /** Schedule state and actions. */
@@ -162,6 +162,15 @@ export interface Codespace {
162
162
  gitStatus: string;
163
163
  }
164
164
 
165
+ /** A running Docker container an environment can attach to (`docker ps`). */
166
+ export interface DockerContainer {
167
+ id: string;
168
+ name: string;
169
+ image: string;
170
+ state: string;
171
+ status: string;
172
+ }
173
+
165
174
  /** An agent or script persona configuration. */
166
175
  export interface PersonaData {
167
176
  id: string;
@@ -496,6 +505,18 @@ export interface UseCodespacesResult {
496
505
  domainHook: DomainHook;
497
506
  }
498
507
 
508
+ /** Values returned by the docker-containers domain hook. */
509
+ export interface UseDockerContainersResult {
510
+ /** Running Docker containers available to attach to. */
511
+ dockerContainers: DockerContainer[];
512
+ /** Error message from the most recent list attempt, or empty string. */
513
+ dockerContainersError: string;
514
+ /** Request the current running-container list from the server. */
515
+ listDockerContainers: () => Promise<void>;
516
+ /** Lifecycle hook for connect/disconnect/event routing. */
517
+ domainHook: DomainHook;
518
+ }
519
+
499
520
  /** Values returned by the personas domain hook. */
500
521
  export interface UsePersonasResult {
501
522
  /** All known personas. */
package/src/index.ts CHANGED
@@ -125,13 +125,13 @@ export type { UseGrackleSocketResult, GrackleContextType } from "./context/Grack
125
125
  export type {
126
126
  Environment, Session, UsageStats, SessionEvent,
127
127
  Workspace, TaskData, FindingData, TokenInfo,
128
- CredentialProviderConfig, Codespace, PersonaData,
128
+ CredentialProviderConfig, Codespace, DockerContainer, PersonaData,
129
129
  ScheduleData, ScheduleUpdate, UseSchedulesResult,
130
130
  ProvisionStatus, GrackleEvent, WsMessage, SendFunction,
131
131
  GraphNode, GraphLink, NodeDetail, UseKnowledgeResult,
132
132
  UseEnvironmentsResult, UseSessionsResult, UseWorkspacesResult,
133
133
  UseTasksResult, UseFindingsResult, UseTokensResult,
134
- UseCredentialsResult, UseCodespacesResult, UsePersonasResult,
134
+ UseCredentialsResult, UseCodespacesResult, UseDockerContainersResult, UsePersonasResult,
135
135
  UsePluginsResult, PluginData,
136
136
  StreamData, StreamSubscriberData, UseStreamsResult,
137
137
  UseGitHubAccountsResult, GitHubAccountData,
@@ -1067,6 +1067,13 @@ export function MockGrackleProvider({ children }: MockGrackleProviderProps): JSX
1067
1067
  domainHook: NOOP_DOMAIN_HOOK,
1068
1068
  },
1069
1069
 
1070
+ dockerContainers: {
1071
+ dockerContainers: [],
1072
+ dockerContainersError: "",
1073
+ listDockerContainers: async () => { },
1074
+ domainHook: NOOP_DOMAIN_HOOK,
1075
+ },
1076
+
1070
1077
  personas: {
1071
1078
  personas,
1072
1079
  personasLoading: false,