@marimo-team/islands 0.23.12-dev2 → 0.23.12-dev21

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 (106) hide show
  1. package/dist/{ConnectedDataExplorerComponent-WqG-xX4l.js → ConnectedDataExplorerComponent-Du3_nUzI.js} +13 -13
  2. package/dist/{ErrorBoundary-BNx_OSVo.js → ErrorBoundary-DE6tzZf-.js} +2 -2
  3. package/dist/{any-language-editor-rPSlOll9.js → any-language-editor-DN1R-1KZ.js} +5 -5
  4. package/dist/{button-vQhauTmO.js → button-BacYv-bE.js} +7 -1
  5. package/dist/{capabilities-BEHzIS99.js → capabilities-D_4LYhSU.js} +1 -1
  6. package/dist/{chat-ui-k2kqhCv5.js → chat-ui-CsPewo4h.js} +16 -16
  7. package/dist/{check-nrzHDi45.js → check-C9OoNtR4.js} +1 -1
  8. package/dist/{code-visibility-DZ_6U5hT.js → code-visibility-DBnAQPtB.js} +664 -663
  9. package/dist/{copy-UhDed7D4.js → copy-COam1EG7.js} +2 -2
  10. package/dist/{dist-DYGLrbYQ.js → dist--2Bqjvs0.js} +2 -2
  11. package/dist/{error-banner-BHAkVFc2.js → error-banner-DFPfz_Qf.js} +2 -2
  12. package/dist/{esm-Bqu9AE2K.js → esm-M837UxV5.js} +1 -1
  13. package/dist/{extends-9Yl5BEcg.js → extends-9MVIxxRo.js} +4 -4
  14. package/dist/{formats-BV4bOfMI.js → formats-d6MhLuQ9.js} +4 -4
  15. package/dist/{glide-data-editor-BDTq6YUb.js → glide-data-editor-DkzAInWG.js} +9 -9
  16. package/dist/{html-to-image-C86pQALH.js → html-to-image-DXwLcQ6l.js} +95 -88
  17. package/dist/{input-AKkGXdyV.js → input-CbEz_aj_.js} +6 -6
  18. package/dist/{label-E3ZJXHu8.js → label-WfTSU8L4.js} +2 -2
  19. package/dist/{loader-YPuQvn1Y.js → loader-Boph2xIS.js} +1 -1
  20. package/dist/main.js +1753 -1626
  21. package/dist/{mermaid-QFAR9YgY.js → mermaid-CJW9vIyO.js} +5 -5
  22. package/dist/{process-output-nNw4OpSj.js → process-output-C6_e1pT_.js} +3 -3
  23. package/dist/{reveal-component-BxDb5eK0.js → reveal-component-DIzKQ1NS.js} +11 -11
  24. package/dist/{spec-B45_YCNI.js → spec-Bv-XlYiv.js} +4 -4
  25. package/dist/{strings-Cq2s9_EQ.js → strings-Dq_j3Rxw.js} +4 -4
  26. package/dist/style.css +2 -2
  27. package/dist/{swiper-component-BNa_4kh2.js → swiper-component-5HoSsPi1.js} +2 -2
  28. package/dist/{toDate-Do1xRzAo.js → toDate-D-l5s8nn.js} +3 -3
  29. package/dist/{tooltip-Bz3OAwrU.js → tooltip-Czds6Qr8.js} +3 -3
  30. package/dist/{types-D8gEGs4R.js → types-C2Ir191_.js} +1 -1
  31. package/dist/{useAsyncData-CL3o2p4i.js → useAsyncData-1Dhzjfwf.js} +1 -1
  32. package/dist/{useDateFormatter-BC6iSz9g.js → useDateFormatter-CMnRuVmN.js} +2 -2
  33. package/dist/{useDeepCompareMemoize-BPx2MuOK.js → useDeepCompareMemoize-CDWT3BDz.js} +1 -1
  34. package/dist/{useIframeCapabilities-C6Ta3EyP.js → useIframeCapabilities-DWIYvDh7.js} +1 -1
  35. package/dist/{useLifecycle-C3Ec71q0.js → useLifecycle-AHlswLw-.js} +3 -3
  36. package/dist/{useTheme-ZhT6uIu3.js → useTheme-BrYvK-_A.js} +2 -2
  37. package/dist/{vega-component-C3AWYGAL.js → vega-component-Pk6lyc_a.js} +10 -10
  38. package/dist/{zod-DXqkaI_w.js → zod-CijjQh4u.js} +1 -1
  39. package/package.json +3 -3
  40. package/src/components/ai/display-helpers.tsx +5 -5
  41. package/src/components/app-config/ai-config.tsx +5 -5
  42. package/src/components/app-config/mcp-config.tsx +3 -3
  43. package/src/components/chat/acp/agent-panel.tsx +3 -3
  44. package/src/components/chat/acp/blocks.tsx +36 -38
  45. package/src/components/chat/acp/common.tsx +12 -16
  46. package/src/components/chat/acp/scroll-to-bottom-button.tsx +1 -1
  47. package/src/components/chat/acp/session-tabs.tsx +2 -2
  48. package/src/components/chat/chat-history-popover.tsx +1 -1
  49. package/src/components/chat/chat-panel.tsx +47 -23
  50. package/src/components/data-table/TableBottomBar.tsx +4 -1
  51. package/src/components/data-table/columns.tsx +2 -2
  52. package/src/components/data-table/data-table.tsx +26 -17
  53. package/src/components/data-table/filter-pill-editor.tsx +1 -1
  54. package/src/components/dependency-graph/minimap-content.tsx +1 -1
  55. package/src/components/editor/RecoveryButton.tsx +1 -1
  56. package/src/components/editor/actions/pair-with-agent-modal.tsx +2 -2
  57. package/src/components/editor/actions/useNotebookActions.tsx +4 -4
  58. package/src/components/editor/ai/__tests__/completion-utils.test.ts +138 -2
  59. package/src/components/editor/ai/ai-completion-editor.tsx +1 -1
  60. package/src/components/editor/ai/completion-utils.ts +124 -21
  61. package/src/components/editor/cell/CreateCellButton.tsx +1 -1
  62. package/src/components/editor/chrome/panels/empty-state.tsx +1 -1
  63. package/src/components/editor/chrome/panels/outline/floating-outline.tsx +1 -1
  64. package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +1 -1
  65. package/src/components/editor/columns/cell-column.tsx +1 -1
  66. package/src/components/editor/columns/sortable-column.tsx +2 -2
  67. package/src/components/editor/output/MarimoErrorOutput.tsx +1 -1
  68. package/src/components/editor/output/TextOutput.tsx +2 -2
  69. package/src/components/home/components.tsx +4 -4
  70. package/src/components/icons/github.tsx +21 -0
  71. package/src/components/icons/youtube.tsx +21 -0
  72. package/src/components/slides/minimap.tsx +2 -2
  73. package/src/components/slides/reveal-component.tsx +1 -1
  74. package/src/components/storage/components.tsx +3 -7
  75. package/src/components/ui/alert.tsx +1 -1
  76. package/src/components/ui/command.tsx +2 -2
  77. package/src/components/ui/reorderable-list.tsx +1 -1
  78. package/src/components/ui/table.tsx +2 -5
  79. package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +67 -0
  80. package/src/core/codemirror/go-to-definition/__tests__/utils.test.ts +47 -0
  81. package/src/core/codemirror/go-to-definition/commands.ts +47 -30
  82. package/src/core/codemirror/go-to-definition/utils.ts +0 -1
  83. package/src/core/codemirror/language/languages/sql/renderers.tsx +60 -68
  84. package/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts +54 -0
  85. package/src/core/codemirror/reactive-references/analyzer.ts +44 -35
  86. package/src/core/hotkeys/hotkeys.ts +1 -0
  87. package/src/core/islands/__tests__/bridge.test.ts +25 -0
  88. package/src/core/islands/__tests__/parse.test.ts +585 -1
  89. package/src/core/islands/__tests__/test-utils.tsx +10 -1
  90. package/src/core/islands/bridge.ts +6 -1
  91. package/src/core/islands/constants.ts +2 -0
  92. package/src/core/islands/parse.ts +293 -13
  93. package/src/plugins/impl/DataTablePlugin.tsx +20 -1
  94. package/src/plugins/impl/FileBrowserPlugin.tsx +165 -74
  95. package/src/plugins/impl/MatrixPlugin.tsx +2 -2
  96. package/src/plugins/impl/TabsPlugin.tsx +1 -1
  97. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +141 -1
  98. package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +314 -0
  99. package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +4 -1
  100. package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +34 -0
  101. package/src/plugins/impl/anywidget/__tests__/model.test.ts +19 -0
  102. package/src/plugins/impl/anywidget/model.ts +15 -0
  103. package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +1 -1
  104. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +155 -98
  105. package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +154 -1
  106. package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +10 -0
@@ -7,8 +7,9 @@ import {
7
7
  startCompletion,
8
8
  } from "@codemirror/autocomplete";
9
9
  import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
10
- import type { FileUIPart } from "ai";
10
+ import type { DataUIPart, FileUIPart, UIMessage } from "ai";
11
11
  import { getAIContextRegistry } from "@/core/ai/context/context";
12
+ import type { ContextLocatorId } from "@/core/ai/context/registry";
12
13
  import { getCodes } from "@/core/codemirror/copilot/getCodes";
13
14
  import type { LanguageAdapterType } from "@/core/codemirror/language/types";
14
15
  import type { AiCompletionRequest } from "@/core/network/types";
@@ -50,37 +51,139 @@ export function getAICompletionBody({
50
51
  };
51
52
  }
52
53
 
54
+ export interface MarimoContextData {
55
+ plainText: string;
56
+ contextIds: string[];
57
+ }
58
+
59
+ export type MarimoContextUIPart = DataUIPart<{
60
+ "marimo-context": MarimoContextData;
61
+ }>;
62
+
63
+ /**
64
+ * Wire `type` of the @-context data part. Must match
65
+ * `MARIMO_CONTEXT_PART_TYPE` on the backend.
66
+ */
67
+ export const MARIMO_CONTEXT_PART_TYPE =
68
+ "data-marimo-context" as const satisfies MarimoContextUIPart["type"];
69
+
70
+ export interface ResolvedChatContext {
71
+ contextPart: MarimoContextUIPart | null;
72
+ attachments: FileUIPart[];
73
+ }
74
+
75
+ /**
76
+ * Marker stamped onto attachments derived from @-context (as opposed to files
77
+ * the user uploaded directly).
78
+ */
79
+ const CONTEXT_ATTACHMENT_METADATA = {
80
+ marimo: { source: "context" },
81
+ } as const;
82
+
83
+ /** Whether a part is an attachment that was derived from @-context. */
84
+ export function isContextAttachment(part: UIMessage["parts"][number]): boolean {
85
+ return (
86
+ part.type === "file" &&
87
+ part.providerMetadata?.marimo?.source ===
88
+ CONTEXT_ATTACHMENT_METADATA.marimo.source
89
+ );
90
+ }
91
+
92
+ /**
93
+ * Stamp a context-derived attachment with a provenance marker.
94
+ *
95
+ * Some @-mentions resolve to file attachments (e.g. a cell's image output),
96
+ * which get appended to the user message right alongside files the user
97
+ * uploaded by hand. Once they're in the message the two are indistinguishable,
98
+ * so we mark the context-derived ones. This matters on message edit: we
99
+ * re-resolve context from the edited text, and `isContextAttachment` lets us
100
+ * drop only the stale context attachments while preserving the user's own
101
+ * uploads
102
+ */
103
+ function stampContextAttachment(attachment: FileUIPart): FileUIPart {
104
+ return {
105
+ ...attachment,
106
+ providerMetadata: {
107
+ ...attachment.providerMetadata,
108
+ // Merge within the `marimo` namespace so we don't clobber any other
109
+ // marimo metadata a provider may have already set.
110
+ marimo: {
111
+ ...attachment.providerMetadata?.marimo,
112
+ ...CONTEXT_ATTACHMENT_METADATA.marimo,
113
+ },
114
+ },
115
+ };
116
+ }
117
+
118
+ interface ResolvedContext {
119
+ plainText: string;
120
+ contextIds: ContextLocatorId[];
121
+ attachments: FileUIPart[];
122
+ }
123
+
124
+ /**
125
+ * Parse @-context for messages
126
+ */
127
+ async function resolveContextAttachments(
128
+ input: string,
129
+ ): Promise<ResolvedContext> {
130
+ if (!input.includes(CONTEXT_TRIGGER)) {
131
+ return { plainText: "", contextIds: [], attachments: [] };
132
+ }
133
+
134
+ const registry = getAIContextRegistry(store);
135
+ const contextIds = registry.parseAllContextIds(input);
136
+ if (contextIds.length === 0) {
137
+ return { plainText: "", contextIds: [], attachments: [] };
138
+ }
139
+
140
+ const plainText = registry.formatContextForAI(contextIds);
141
+
142
+ let attachments: FileUIPart[] = [];
143
+ try {
144
+ const resolved = await registry.getAttachmentsForContext(contextIds);
145
+ attachments = resolved.map(stampContextAttachment);
146
+ } catch (error) {
147
+ Logger.error("Error getting attachments:", error);
148
+ }
149
+
150
+ return { plainText, contextIds, attachments };
151
+ }
152
+
153
+ /**
154
+ * Resolve @-context for messages. They represent referenced
155
+ * datasets, variables, or other context from the user's prompt.
156
+ */
157
+ export async function resolveChatContext(
158
+ input: string,
159
+ ): Promise<ResolvedChatContext> {
160
+ const { plainText, contextIds, attachments } =
161
+ await resolveContextAttachments(input);
162
+
163
+ let contextPart: MarimoContextUIPart | null = null;
164
+ if (plainText.trim()) {
165
+ contextPart = {
166
+ type: MARIMO_CONTEXT_PART_TYPE,
167
+ data: { plainText, contextIds: contextIds.map(String) },
168
+ };
169
+ }
170
+
171
+ return { contextPart, attachments };
172
+ }
173
+
53
174
  /**
54
175
  * Gets the request body and attachments for the AI completion API.
55
176
  */
56
177
  export async function getAICompletionBodyWithAttachments({
57
178
  input,
58
179
  }: Opts): Promise<AICompletionBodyWithAttachments> {
59
- let contextString = "";
60
- let attachments: FileUIPart[] = [];
61
-
62
- // Skip if no '@' in the input
63
- if (input.includes("@")) {
64
- const registry = getAIContextRegistry(store);
65
- const contextIds = registry.parseAllContextIds(input);
66
-
67
- // Get context string
68
- contextString = registry.formatContextForAI(contextIds);
69
-
70
- // Get attachments
71
- try {
72
- attachments = await registry.getAttachmentsForContext(contextIds);
73
- Logger.debug("Included attachments", attachments.length);
74
- } catch (error) {
75
- Logger.error("Error getting attachments:", error);
76
- }
77
- }
180
+ const { plainText, attachments } = await resolveContextAttachments(input);
78
181
 
79
182
  return {
80
183
  body: {
81
184
  includeOtherCode: getCodes(""),
82
185
  context: {
83
- plainText: contextString,
186
+ plainText,
84
187
  schema: [],
85
188
  variables: [],
86
189
  },
@@ -142,7 +142,7 @@ export const CreateCellButton = ({
142
142
  <DropdownMenuTrigger asChild={true}>
143
143
  <Button
144
144
  className={cn(
145
- "border-none hover-action shadow-none! bg-transparent! focus-visible:outline-none",
145
+ "border-none hover-action shadow-none! bg-transparent! focus-visible:outline-hidden",
146
146
  isAppInteractionDisabled(connectionState) && " inactive-button",
147
147
  )}
148
148
  onPointerDownCapture={handlePointerDownCapture}
@@ -20,7 +20,7 @@ export const PanelEmptyState = ({
20
20
  {icon &&
21
21
  // oxlint-disable-next-line react/no-clone-element
22
22
  React.cloneElement(icon, {
23
- className: "text-accent-foreground flex-shrink-0",
23
+ className: "text-accent-foreground shrink-0",
24
24
  })}
25
25
  <span className="mt-1 text-accent-foreground">{title}</span>
26
26
  </div>
@@ -38,7 +38,7 @@ export const FloatingOutline: React.FC = () => {
38
38
  <OutlineList
39
39
  className={cn(
40
40
  "-top-4 max-h-[70vh] bg-background rounded-lg shadow-lg absolute overflow-auto transition-all duration-300 w-[300px] border",
41
- isHovered ? "-left-[280px] opacity-100" : "left-[300px] opacity-0",
41
+ isHovered ? "left-[-280px] opacity-100" : "left-[300px] opacity-0",
42
42
  )}
43
43
  items={items}
44
44
  activeHeaderId={activeHeaderId}
@@ -74,7 +74,7 @@ export const PendingAICells: React.FC = () => {
74
74
  <Button variant="ghost" size="icon" onClick={() => clickNext("up")}>
75
75
  <ChevronUp className="h-3.5 w-3.5" />
76
76
  </Button>
77
- <span className="text-xs font-mono min-w-[3.5rem] text-center">
77
+ <span className="text-xs font-mono min-w-14 text-center">
78
78
  {currentIndex === null
79
79
  ? `${listStagedCells.length} pending`
80
80
  : `${currentIndex + 1} / ${listStagedCells.length}`}
@@ -81,7 +81,7 @@ const ResizableComponent = ({
81
81
  <div
82
82
  ref={ref}
83
83
  className={`w-[3px] cursor-col-resize transition-colors duration-200 z-100
84
- relative before:content-[''] before:absolute before:inset-y-0 before:-left-[3px]
84
+ relative before:content-[''] before:absolute before:inset-y-0 before:left-[-3px]
85
85
  before:right-[-3px] before:w-[9px] before:z-[-1]
86
86
  hover/column:bg-[var(--slate-3)] dark:hover/column:bg-[var(--slate-5)]
87
87
  hover/column:hover:bg-primary/60 dark:hover/column:hover:bg-primary/60`}
@@ -78,7 +78,7 @@ const SortableColumnInternal = React.forwardRef(
78
78
  };
79
79
 
80
80
  const dragHandle = (
81
- <div className="px-2 pb-0 group flex items-center overflow-hidden border-b border-[var(--slate-7)]">
81
+ <div className="px-2 pb-0 group flex items-center overflow-hidden border-b border-(--slate-7)">
82
82
  <Tooltip content="Move column left" side="top" delayDuration={300}>
83
83
  <Button
84
84
  variant="text"
@@ -161,7 +161,7 @@ const SortableColumnInternal = React.forwardRef(
161
161
  isOver && "bg-accent/20", // Add a background color when dragging over
162
162
  )}
163
163
  >
164
- <div className="border border-[var(--slate-7)]">
164
+ <div className="border border-(--slate-7)">
165
165
  {dragHandle}
166
166
  {props.children}
167
167
  </div>
@@ -722,7 +722,7 @@ export const MarimoErrorOutput = ({
722
722
  <Alert
723
723
  variant={alertVariant}
724
724
  className={cn(
725
- "border-none font-code text-sm text-[0.84375rem] p-0 text-muted-foreground normal [&:has(svg)]:pl-0 space-y-2",
725
+ "border-none font-code text-sm text-[0.84375rem] p-0 text-muted-foreground normal has-[svg]:pl-0 space-y-2",
726
726
  className,
727
727
  )}
728
728
  >
@@ -18,7 +18,7 @@ export const TextOutput = ({ text, channel, wrapText }: Props): JSX.Element => {
18
18
  return (
19
19
  <span
20
20
  className={
21
- wrapText ? "whitespace-pre-wrap break-words" : "whitespace-pre"
21
+ wrapText ? "whitespace-pre-wrap wrap-break-word" : "whitespace-pre"
22
22
  }
23
23
  >
24
24
  <RenderTextWithLinks text={text} />
@@ -30,7 +30,7 @@ export const TextOutput = ({ text, channel, wrapText }: Props): JSX.Element => {
30
30
  <span
31
31
  className={cn(
32
32
  !shouldRenderAnsi &&
33
- (wrapText ? "whitespace-pre-wrap break-words" : "whitespace-pre"),
33
+ (wrapText ? "whitespace-pre-wrap wrap-break-word" : "whitespace-pre"),
34
34
  channel === "output" && "font-prose",
35
35
  channel,
36
36
  )}
@@ -9,7 +9,6 @@ import {
9
9
  DatabaseIcon,
10
10
  FileIcon,
11
11
  FileTextIcon,
12
- GithubIcon,
13
12
  GraduationCapIcon,
14
13
  GridIcon,
15
14
  LayoutIcon,
@@ -17,10 +16,11 @@ import {
17
16
  MessagesSquareIcon,
18
17
  OrbitIcon,
19
18
  PackageIcon,
20
- YoutubeIcon,
21
19
  } from "lucide-react";
22
20
  import type React from "react";
23
21
  import { MarkdownIcon } from "@/components/editor/cell/code/icons";
22
+ import { GitHubIcon } from "@/components/icons/github";
23
+ import { YouTubeIcon } from "@/components/icons/youtube";
24
24
  import { Button } from "@/components/ui/button";
25
25
  import {
26
26
  DropdownMenu,
@@ -130,7 +130,7 @@ const RESOURCES = [
130
130
  {
131
131
  title: "GitHub",
132
132
  description: "View source code, report issues, or contribute",
133
- icon: GithubIcon,
133
+ icon: GitHubIcon,
134
134
  url: Constants.githubPage,
135
135
  },
136
136
  {
@@ -148,7 +148,7 @@ const RESOURCES = [
148
148
  {
149
149
  title: "YouTube",
150
150
  description: "Watch tutorials and demos",
151
- icon: YoutubeIcon,
151
+ icon: YouTubeIcon,
152
152
  url: Constants.youtube,
153
153
  },
154
154
  {
@@ -0,0 +1,21 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import type { SVGProps } from "react";
3
+
4
+ // Artwork from Simple Icons (https://simpleicons.org/?q=github), licensed CC0 1.0.
5
+ // The GitHub name and logo are trademarks of GitHub, Inc.
6
+ export const GitHubIcon = (props: SVGProps<SVGSVGElement>) => {
7
+ return (
8
+ <svg
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ width="1em"
11
+ height="1em"
12
+ viewBox="0 0 24 24"
13
+ fill="currentColor"
14
+ aria-hidden="true"
15
+ focusable="false"
16
+ {...props}
17
+ >
18
+ <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
19
+ </svg>
20
+ );
21
+ };
@@ -0,0 +1,21 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+ import type { SVGProps } from "react";
3
+
4
+ // Artwork from Simple Icons (https://simpleicons.org/?q=youtube), licensed CC0 1.0.
5
+ // The YouTube name and logo are trademarks of Google LLC.
6
+ export const YouTubeIcon = (props: SVGProps<SVGSVGElement>) => {
7
+ return (
8
+ <svg
9
+ xmlns="http://www.w3.org/2000/svg"
10
+ width="1em"
11
+ height="1em"
12
+ viewBox="0 0 24 24"
13
+ fill="currentColor"
14
+ aria-hidden="true"
15
+ focusable="false"
16
+ {...props}
17
+ >
18
+ <path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
19
+ </svg>
20
+ );
21
+ };
@@ -514,7 +514,7 @@ const SlideThumbnailRow = ({
514
514
  tabIndex={0}
515
515
  data-cell-id={cell.id}
516
516
  className={cn(
517
- "relative shrink-0 appearance-none text-left p-0 bg-transparent outline-none",
517
+ "relative shrink-0 appearance-none text-left p-0 bg-transparent outline-hidden",
518
518
  className,
519
519
  )}
520
520
  style={rowStyle}
@@ -591,7 +591,7 @@ const InsertCellLine = ({
591
591
  data-testid="minimap-insert-cell"
592
592
  className={cn(
593
593
  "absolute left-0 right-0 z-30 flex h-3 items-center justify-center",
594
- "opacity-0 transition-opacity hover:opacity-80 focus-visible:opacity-100 focus:outline-none",
594
+ "opacity-0 transition-opacity hover:opacity-80 focus-visible:opacity-100 focus:outline-hidden",
595
595
  position === "below"
596
596
  ? "bottom-0 translate-y-1/2"
597
597
  : "top-0 -translate-y-1/2",
@@ -684,7 +684,7 @@ const RevealSlidesComponent = ({
684
684
  <div className="group relative" style={{ width, height }}>
685
685
  <Deck
686
686
  deckRef={deckRef}
687
- className="aspect-video w-full overflow-hidden border rounded bg-background mo-slides-theme prose-slides focus:outline-none focus-visible:outline-none"
687
+ className="aspect-video w-full overflow-hidden border rounded bg-background mo-slides-theme prose-slides focus:outline-hidden focus-visible:outline-hidden"
688
688
  config={revealConfig}
689
689
  onReady={handleDeckReady}
690
690
  onSlideChange={handleSlideChange}
@@ -6,14 +6,10 @@ import AzureIcon from "@marimo-team/llm-info/icons/azure.svg?inline";
6
6
  import CloudflareIcon from "@marimo-team/llm-info/icons/cloudflare.svg?inline";
7
7
  import CoreweaveIcon from "@marimo-team/llm-info/icons/coreweave.svg?inline";
8
8
  import CoreweaveDarkIcon from "@marimo-team/llm-info/icons/coreweave-dark.svg?inline";
9
- import {
10
- DatabaseZapIcon,
11
- GithubIcon,
12
- GlobeIcon,
13
- HardDriveIcon,
14
- } from "lucide-react";
9
+ import { DatabaseZapIcon, GlobeIcon, HardDriveIcon } from "lucide-react";
15
10
  import GoogleCloudIcon from "@/components/databases/icons/google-cloud-storage.svg?inline";
16
11
  import GoogleDriveIcon from "@/components/databases/icons/google-drive.svg?inline";
12
+ import { GitHubIcon } from "@/components/icons/github";
17
13
  import type { KnownStorageProtocol } from "@/core/storage/types";
18
14
  import { useTheme } from "@/theme/useTheme";
19
15
  import { cn } from "@/utils/cn";
@@ -32,7 +28,7 @@ const PROTOCOL_ICONS: Record<KnownStorageProtocol, IconEntry> = {
32
28
  file: HardDriveIcon,
33
29
  "in-memory": DatabaseZapIcon,
34
30
  gdrive: { src: GoogleDriveIcon },
35
- github: GithubIcon,
31
+ github: GitHubIcon,
36
32
  };
37
33
 
38
34
  export const ProtocolIcon: React.FC<{
@@ -6,7 +6,7 @@ import * as React from "react";
6
6
  import { cn } from "@/utils/cn";
7
7
 
8
8
  const alertVariants = cva(
9
- "relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11",
9
+ "relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] has-[svg]:pl-11",
10
10
  {
11
11
  variants: {
12
12
  variant: {
@@ -44,7 +44,7 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
44
44
  className="overflow-hidden p-0 shadow-2xl"
45
45
  usePortal={true}
46
46
  >
47
- <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
47
+ <Command className="**:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 **:[[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 **:[[cmdk-input]]:h-12 **:[[cmdk-item]]:px-2 **:[[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
48
48
  {children}
49
49
  </Command>
50
50
  </DialogContent>
@@ -113,7 +113,7 @@ const CommandGroup = React.forwardRef<
113
113
  <CommandPrimitive.Group
114
114
  ref={ref}
115
115
  className={cn(
116
- "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
116
+ "overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground",
117
117
  className,
118
118
  )}
119
119
  {...props}
@@ -300,7 +300,7 @@ export const ReorderableList = <T extends object>({
300
300
  <ListBoxItem
301
301
  key={getKey(item)}
302
302
  id={getKey(item)}
303
- className="active:cursor-grabbing data-[dragging]:opacity-60 outline-none"
303
+ className="active:cursor-grabbing data-dragging:opacity-60 outline-hidden"
304
304
  onHoverStart={
305
305
  onItemPreloadHint ? () => onItemPreloadHint(item) : undefined
306
306
  }
@@ -75,7 +75,7 @@ const TableHead = React.forwardRef<
75
75
  <th
76
76
  ref={ref}
77
77
  className={cn(
78
- "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
78
+ "h-10 px-2 text-left align-middle font-medium text-muted-foreground has-[[role=checkbox]]:pr-0",
79
79
  className,
80
80
  )}
81
81
  {...props}
@@ -89,10 +89,7 @@ const TableCell = React.forwardRef<
89
89
  >(({ className, ...props }, ref) => (
90
90
  <td
91
91
  ref={ref}
92
- className={cn(
93
- "p-1.5 align-middle [&:has([role=checkbox])]:pr-0",
94
- className,
95
- )}
92
+ className={cn("p-1.5 align-middle has-[[role=checkbox]]:pr-0", className)}
96
93
  {...props}
97
94
  />
98
95
  ));
@@ -253,6 +253,73 @@ a = 10`;
253
253
  `);
254
254
  });
255
255
 
256
+ test("from-import alias is the binding, not the imported name", async () => {
257
+ const code = `\
258
+ from math import sin as my_sin
259
+ print(my_sin)`;
260
+ view = createEditor(code);
261
+ const usagePosition = code.lastIndexOf("my_sin");
262
+ const result = goToVariableDefinition(view, "my_sin", usagePosition);
263
+
264
+ expect(result).toBe(true);
265
+ await tick();
266
+ // The alias `my_sin` (after `as`) is the real binding.
267
+ expect(renderEditorView(view)).toMatchInlineSnapshot(`
268
+ "
269
+ from math import sin as my_sin
270
+ ^
271
+ print(my_sin)
272
+ "
273
+ `);
274
+ });
275
+
276
+ test("module path in from-import is not a local definition", async () => {
277
+ const code = `\
278
+ from math import sin
279
+ print(math)`;
280
+ view = createEditor(code);
281
+ const usagePosition = code.lastIndexOf("math");
282
+ // `math` is a module reference in the from-clause, not a binding in this
283
+ // cell, so the scoped resolver should return false and let the caller fall
284
+ // through to cross-cell resolution.
285
+ const result = goToVariableDefinition(view, "math", usagePosition);
286
+
287
+ expect(result).toBe(false);
288
+ expect(view.state.selection.main.head).toBe(0);
289
+ });
290
+
291
+ test("imported name without `as` is a local definition", async () => {
292
+ const code = `\
293
+ from math import sin
294
+ print(sin)`;
295
+ view = createEditor(code);
296
+ const usagePosition = code.lastIndexOf("sin");
297
+ const result = goToVariableDefinition(view, "sin", usagePosition);
298
+
299
+ expect(result).toBe(true);
300
+ await tick();
301
+ expect(renderEditorView(view)).toMatchInlineSnapshot(`
302
+ "
303
+ from math import sin
304
+ ^
305
+ print(sin)
306
+ "
307
+ `);
308
+ });
309
+
310
+ test("imported name shadowed by `as` is not a binding", async () => {
311
+ const code = `\
312
+ from math import sin as my_sin
313
+ print(sin)`;
314
+ view = createEditor(code);
315
+ const usagePosition = code.lastIndexOf("sin");
316
+ // `sin` here refers to nothing in this cell (it was renamed to `my_sin`),
317
+ // so the scoped resolver should return false.
318
+ const result = goToVariableDefinition(view, "sin", usagePosition);
319
+
320
+ expect(result).toBe(false);
321
+ });
322
+
256
323
  test("selects outer-scope function declaration", async () => {
257
324
  view = createEditor(`\
258
325
  def x():
@@ -133,4 +133,51 @@ output = _x + 10`;
133
133
  await tick();
134
134
  expect(view.state.selection.main.head).toBe(code.indexOf("_x = 10"));
135
135
  });
136
+
137
+ test("falls through to cross-cell when in-cell occurrence is only a module path in a from-import", async () => {
138
+ // Regression: ImportStatement used to register every VariableName child
139
+ // (the module path and pre-`as` names) as in-cell declarations, so the
140
+ // local-first short-circuit would steal F12 from cross-cell resolution.
141
+ const moduleCell = cellId("module-cell");
142
+ const usageCell = cellId("usage-cell");
143
+ const moduleCode = `mymodule = 100`;
144
+ const usageCode = `\
145
+ from mymodule import something
146
+ print(mymodule)`;
147
+
148
+ const moduleView = createEditor(moduleCode, moduleCode.length);
149
+ const usageView = createEditor(
150
+ usageCode,
151
+ usageCode.lastIndexOf("mymodule"),
152
+ );
153
+ views.push(moduleView, usageView);
154
+
155
+ const notebook = initialNotebookState();
156
+ notebook.cellHandles[moduleCell] = {
157
+ current: { editorView: moduleView, editorViewOrNull: moduleView },
158
+ } as never;
159
+ notebook.cellHandles[usageCell] = {
160
+ current: { editorView: usageView, editorViewOrNull: usageView },
161
+ } as never;
162
+
163
+ store.set(notebookAtom, notebook);
164
+ store.set(variablesAtom, {
165
+ [variableName("mymodule")]: {
166
+ dataType: "int",
167
+ declaredBy: [moduleCell],
168
+ name: variableName("mymodule"),
169
+ usedBy: [usageCell],
170
+ value: "100",
171
+ },
172
+ });
173
+
174
+ const result = goToDefinitionAtCursorPosition(usageView);
175
+
176
+ expect(result).toBe(true);
177
+ await tick();
178
+ // Cross-cell jump: moduleView's cursor should land on `mymodule = 100`.
179
+ expect(moduleView.state.selection.main.head).toBe(
180
+ moduleCode.indexOf("mymodule"),
181
+ );
182
+ });
136
183
  });