@marimo-team/islands 0.23.11-dev12 → 0.23.11-dev18
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.
- package/dist/{ConnectedDataExplorerComponent-Z3RP7Vmm.js → ConnectedDataExplorerComponent-BnPKA-xR.js} +4 -4
- package/dist/assets/__vite-browser-external-DAm_YW43.js +1 -0
- package/dist/assets/{worker-CC0Oul9k.js → worker-DEDLIQQV.js} +2 -2
- package/dist/{chat-ui-ZCkzXjpY.js → chat-ui-Bf2NICxi.js} +6 -6
- package/dist/{code-visibility-w2IqZ0FR.js → code-visibility-BCzDeiYd.js} +8 -8
- package/dist/{formats-C4wO47tk.js → formats-BOYEeoL_.js} +1 -1
- package/dist/{glide-data-editor-BPkCPs7L.js → glide-data-editor-A_l7_PQZ.js} +2 -2
- package/dist/{html-to-image-AwZNbzIe.js → html-to-image-BJbLjZyG.js} +5 -5
- package/dist/{input-OdWHkobi.js → input-Cn-SZdXY.js} +1 -1
- package/dist/main.js +18 -18
- package/dist/{mermaid-BotvIKg9.js → mermaid-Br973nIL.js} +2 -2
- package/dist/{process-output-BnIUIlAz.js → process-output-Dw_ngzH2.js} +1 -1
- package/dist/{reveal-component-CJnarjEW.js → reveal-component-DchNN1ES.js} +5 -5
- package/dist/{spec-X7FwLJni.js → spec-f8-xgaui.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{toDate-d8RCRrRd.js → toDate-B4-pUFYh.js} +1 -1
- package/dist/{useAsyncData-PonK__yh.js → useAsyncData-B3PfqrwD.js} +1 -1
- package/dist/{useDeepCompareMemoize-D3NGWke6.js → useDeepCompareMemoize-YInAvJP3.js} +1 -1
- package/dist/{useLifecycle-00mO3OSS.js → useLifecycle-B3NOEiY5.js} +1 -1
- package/dist/{useTheme-DEhDzATN.js → useTheme-BPeFKs1Q.js} +2 -1
- package/dist/{vega-component-DGPUhbDs.js → vega-component-CMicMpWX.js} +5 -5
- package/package.json +1 -1
- package/src/components/ai/ai-model-dropdown.tsx +6 -0
- package/src/components/app-config/ai-config.tsx +67 -54
- package/src/components/app-config/data-form.tsx +33 -43
- package/src/components/app-config/is-overridden.tsx +125 -58
- package/src/components/app-config/user-config-form.tsx +178 -227
- package/src/components/chat/chat-panel.tsx +17 -8
- package/src/components/datasources/__tests__/utils.test.ts +127 -2
- package/src/components/datasources/datasources.tsx +44 -17
- package/src/components/datasources/utils.ts +45 -0
- package/src/core/config/config-schema.ts +6 -1
- package/src/css/md.css +12 -0
- package/dist/assets/__vite-browser-external-eshhtsgZ.js +0 -1
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
PlusIcon,
|
|
24
24
|
SparklesIcon,
|
|
25
25
|
SettingsIcon,
|
|
26
|
+
CodeIcon,
|
|
26
27
|
} from "lucide-react";
|
|
27
28
|
import { memo, useEffect, useRef, useState } from "react";
|
|
28
29
|
import useEvent from "react-use-event-hook";
|
|
@@ -268,24 +269,32 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
|
|
|
268
269
|
{
|
|
269
270
|
value: "ask",
|
|
270
271
|
label: "Ask",
|
|
271
|
-
subtitle:
|
|
272
|
-
"Use AI with access to read-only tools like documentation search",
|
|
272
|
+
subtitle: "AI with access to read-only tools like documentation search",
|
|
273
273
|
Icon: NotebookText,
|
|
274
274
|
},
|
|
275
275
|
{
|
|
276
276
|
value: "agent",
|
|
277
|
-
label: "Agent
|
|
278
|
-
subtitle: "
|
|
277
|
+
label: "Agent",
|
|
278
|
+
subtitle: "AI with access to read and write tools",
|
|
279
279
|
Icon: HatGlasses,
|
|
280
280
|
},
|
|
281
281
|
];
|
|
282
282
|
|
|
283
|
+
if (import.meta.env.DEV) {
|
|
284
|
+
modeOptions.push({
|
|
285
|
+
value: "code_mode",
|
|
286
|
+
label: "Code Mode (experimental)",
|
|
287
|
+
subtitle: "AI with access to the notebook's kernel. Use with caution.",
|
|
288
|
+
Icon: CodeIcon,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
283
292
|
const isAttachmentSupported =
|
|
284
293
|
PROVIDERS_THAT_SUPPORT_ATTACHMENTS.has(currentProvider);
|
|
285
294
|
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
295
|
+
const currentModeOption = modeOptions.find((o) => o.value === currentMode);
|
|
296
|
+
const CurrentModeIcon = currentModeOption?.Icon;
|
|
297
|
+
const CurrentModeLabel = currentModeOption?.label;
|
|
289
298
|
|
|
290
299
|
return (
|
|
291
300
|
<TooltipProvider>
|
|
@@ -294,7 +303,7 @@ const ChatInputFooter: React.FC<ChatInputFooterProps> = memo(
|
|
|
294
303
|
<Select value={currentMode} onValueChange={saveModeChange}>
|
|
295
304
|
<SelectTrigger className="h-6 text-xs border-border shadow-none! ring-0! bg-muted hover:bg-muted/30 py-0 px-2 gap-1.5">
|
|
296
305
|
{CurrentModeIcon && <CurrentModeIcon className="h-3 w-3" />}
|
|
297
|
-
<span
|
|
306
|
+
<span>{CurrentModeLabel}</span>
|
|
298
307
|
</SelectTrigger>
|
|
299
308
|
<SelectContent>
|
|
300
309
|
<SelectGroup>
|
|
@@ -3,8 +3,18 @@
|
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
4
|
import type { SQLTableContext } from "@/core/datasets/data-source-connections";
|
|
5
5
|
import { DUCKDB_ENGINE } from "@/core/datasets/engines";
|
|
6
|
-
import type {
|
|
7
|
-
|
|
6
|
+
import type {
|
|
7
|
+
Database,
|
|
8
|
+
DatabaseSchema,
|
|
9
|
+
DataTable,
|
|
10
|
+
DataTableColumn,
|
|
11
|
+
} from "@/core/kernel/messages";
|
|
12
|
+
import {
|
|
13
|
+
schemaSubtreeMatchesSearch,
|
|
14
|
+
shouldExpandDatabaseForSearch,
|
|
15
|
+
sqlCode,
|
|
16
|
+
tableUniqueId,
|
|
17
|
+
} from "../utils";
|
|
8
18
|
|
|
9
19
|
describe("sqlCode", () => {
|
|
10
20
|
const mockTable: DataTable = {
|
|
@@ -519,3 +529,118 @@ describe("tableUniqueId", () => {
|
|
|
519
529
|
);
|
|
520
530
|
});
|
|
521
531
|
});
|
|
532
|
+
|
|
533
|
+
function makeTable(name: string): DataTable {
|
|
534
|
+
return {
|
|
535
|
+
name,
|
|
536
|
+
columns: [],
|
|
537
|
+
source: "memory",
|
|
538
|
+
source_type: "local",
|
|
539
|
+
type: "table",
|
|
540
|
+
engine: null,
|
|
541
|
+
indexes: null,
|
|
542
|
+
num_columns: null,
|
|
543
|
+
num_rows: null,
|
|
544
|
+
variable_name: null,
|
|
545
|
+
primary_keys: null,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function makeSchema(opts: {
|
|
550
|
+
name: string;
|
|
551
|
+
tables: DataTable[];
|
|
552
|
+
tables_resolved?: boolean;
|
|
553
|
+
child_schemas?: DatabaseSchema[];
|
|
554
|
+
child_schemas_resolved?: boolean;
|
|
555
|
+
}): DatabaseSchema {
|
|
556
|
+
return {
|
|
557
|
+
name: opts.name,
|
|
558
|
+
tables: opts.tables,
|
|
559
|
+
tables_resolved: opts.tables_resolved ?? true,
|
|
560
|
+
child_schemas: opts.child_schemas ?? [],
|
|
561
|
+
child_schemas_resolved: opts.child_schemas_resolved ?? true,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
function makeDatabase(
|
|
566
|
+
name: string,
|
|
567
|
+
schemas: DatabaseSchema[],
|
|
568
|
+
schemas_resolved = true,
|
|
569
|
+
): Database {
|
|
570
|
+
return { name, dialect: "duckdb", schemas, schemas_resolved, engine: null };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
describe("schemaSubtreeMatchesSearch", () => {
|
|
574
|
+
it("returns false for an empty query", () => {
|
|
575
|
+
const schema = makeSchema({ name: "main", tables: [makeTable("users")] });
|
|
576
|
+
expect(schemaSubtreeMatchesSearch(schema, "")).toBe(false);
|
|
577
|
+
expect(schemaSubtreeMatchesSearch(schema, " ")).toBe(false);
|
|
578
|
+
expect(schemaSubtreeMatchesSearch(schema, undefined)).toBe(false);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it("matches a table name case-insensitively", () => {
|
|
582
|
+
const schema = makeSchema({ name: "main", tables: [makeTable("Users")] });
|
|
583
|
+
expect(schemaSubtreeMatchesSearch(schema, "user")).toBe(true);
|
|
584
|
+
expect(schemaSubtreeMatchesSearch(schema, "orders")).toBe(false);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it("matches a table in a resolved child schema", () => {
|
|
588
|
+
const schema = makeSchema({
|
|
589
|
+
name: "parent",
|
|
590
|
+
tables: [],
|
|
591
|
+
child_schemas: [
|
|
592
|
+
makeSchema({ name: "child", tables: [makeTable("orders")] }),
|
|
593
|
+
],
|
|
594
|
+
});
|
|
595
|
+
expect(schemaSubtreeMatchesSearch(schema, "orders")).toBe(true);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it("ignores deferred tables so search never triggers a fetch", () => {
|
|
599
|
+
const schema = makeSchema({
|
|
600
|
+
name: "main",
|
|
601
|
+
tables: [makeTable("users")],
|
|
602
|
+
tables_resolved: false,
|
|
603
|
+
});
|
|
604
|
+
expect(schemaSubtreeMatchesSearch(schema, "users")).toBe(false);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
it("ignores deferred child schemas", () => {
|
|
608
|
+
const schema = makeSchema({
|
|
609
|
+
name: "parent",
|
|
610
|
+
tables: [],
|
|
611
|
+
child_schemas: [
|
|
612
|
+
makeSchema({ name: "child", tables: [makeTable("orders")] }),
|
|
613
|
+
],
|
|
614
|
+
child_schemas_resolved: false,
|
|
615
|
+
});
|
|
616
|
+
expect(schemaSubtreeMatchesSearch(schema, "orders")).toBe(false);
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
describe("shouldExpandDatabaseForSearch", () => {
|
|
621
|
+
it("returns false for an empty query", () => {
|
|
622
|
+
const db = makeDatabase("memory", [
|
|
623
|
+
makeSchema({ name: "main", tables: [makeTable("users")] }),
|
|
624
|
+
]);
|
|
625
|
+
expect(shouldExpandDatabaseForSearch(db, "")).toBe(false);
|
|
626
|
+
expect(shouldExpandDatabaseForSearch(db, undefined)).toBe(false);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it("expands when a loaded schema contains a matching table", () => {
|
|
630
|
+
const db = makeDatabase("memory", [
|
|
631
|
+
makeSchema({ name: "main", tables: [makeTable("users")] }),
|
|
632
|
+
makeSchema({ name: "other", tables: [makeTable("orders")] }),
|
|
633
|
+
]);
|
|
634
|
+
expect(shouldExpandDatabaseForSearch(db, "user")).toBe(true);
|
|
635
|
+
expect(shouldExpandDatabaseForSearch(db, "nomatch")).toBe(false);
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
it("does not expand when the schema list itself is deferred", () => {
|
|
639
|
+
const db = makeDatabase(
|
|
640
|
+
"memory",
|
|
641
|
+
[makeSchema({ name: "main", tables: [makeTable("users")] })],
|
|
642
|
+
/* schemas_resolved */ false,
|
|
643
|
+
);
|
|
644
|
+
expect(shouldExpandDatabaseForSearch(db, "users")).toBe(false);
|
|
645
|
+
});
|
|
646
|
+
});
|
|
@@ -83,6 +83,8 @@ import {
|
|
|
83
83
|
areSchemasResolved,
|
|
84
84
|
areTablesResolved,
|
|
85
85
|
isSchemaless,
|
|
86
|
+
schemaSubtreeMatchesSearch,
|
|
87
|
+
shouldExpandDatabaseForSearch,
|
|
86
88
|
sqlCode,
|
|
87
89
|
tableUniqueId,
|
|
88
90
|
} from "./utils";
|
|
@@ -360,7 +362,6 @@ export const DataSources: React.FC = () => {
|
|
|
360
362
|
key={database.name}
|
|
361
363
|
connection={connection}
|
|
362
364
|
database={database}
|
|
363
|
-
hasSearch={hasSearch}
|
|
364
365
|
searchValue={searchValue}
|
|
365
366
|
/>
|
|
366
367
|
))}
|
|
@@ -435,7 +436,6 @@ interface DataSourceTree {
|
|
|
435
436
|
dialect: string;
|
|
436
437
|
engineName: string;
|
|
437
438
|
databaseName: string;
|
|
438
|
-
hasSearch: boolean;
|
|
439
439
|
searchValue?: string;
|
|
440
440
|
}
|
|
441
441
|
|
|
@@ -470,9 +470,8 @@ function buildSqlTableContext(
|
|
|
470
470
|
const DatabaseTree: React.FC<{
|
|
471
471
|
connection: DataSourceConnection;
|
|
472
472
|
database: Database;
|
|
473
|
-
hasSearch: boolean;
|
|
474
473
|
searchValue?: string;
|
|
475
|
-
}> = ({ connection, database,
|
|
474
|
+
}> = ({ connection, database, searchValue }) => {
|
|
476
475
|
const tree = React.useMemo<DataSourceTree>(
|
|
477
476
|
() => ({
|
|
478
477
|
engineName: connection.name,
|
|
@@ -480,7 +479,6 @@ const DatabaseTree: React.FC<{
|
|
|
480
479
|
dialect: connection.dialect,
|
|
481
480
|
defaultSchema: connection.default_schema,
|
|
482
481
|
defaultDatabase: connection.default_database,
|
|
483
|
-
hasSearch,
|
|
484
482
|
searchValue,
|
|
485
483
|
}),
|
|
486
484
|
[
|
|
@@ -489,7 +487,6 @@ const DatabaseTree: React.FC<{
|
|
|
489
487
|
connection.default_schema,
|
|
490
488
|
connection.default_database,
|
|
491
489
|
database.name,
|
|
492
|
-
hasSearch,
|
|
493
490
|
searchValue,
|
|
494
491
|
],
|
|
495
492
|
);
|
|
@@ -498,7 +495,7 @@ const DatabaseTree: React.FC<{
|
|
|
498
495
|
<DatabaseItem
|
|
499
496
|
engineName={connection.name}
|
|
500
497
|
database={database}
|
|
501
|
-
|
|
498
|
+
searchValue={searchValue}
|
|
502
499
|
>
|
|
503
500
|
<DataSourceTreeContext.Provider value={tree}>
|
|
504
501
|
<SchemaList
|
|
@@ -513,18 +510,30 @@ const DatabaseTree: React.FC<{
|
|
|
513
510
|
};
|
|
514
511
|
|
|
515
512
|
const DatabaseItem: React.FC<{
|
|
516
|
-
|
|
513
|
+
searchValue?: string;
|
|
517
514
|
engineName: string;
|
|
518
515
|
database: Database;
|
|
519
516
|
children: React.ReactNode;
|
|
520
|
-
}> = ({
|
|
521
|
-
|
|
517
|
+
}> = ({ searchValue, engineName, database, children }) => {
|
|
518
|
+
// Auto-expand during search only when a loaded schema under this database
|
|
519
|
+
// contains a matching table.
|
|
520
|
+
const expandForSearch = shouldExpandDatabaseForSearch(database, searchValue);
|
|
521
|
+
const [isExpanded, setIsExpanded] = React.useState(expandForSearch);
|
|
522
522
|
const [isSelected, setIsSelected] = React.useState(false);
|
|
523
|
-
const [
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
523
|
+
const [prevSearchValue, setPrevSearchValue] = React.useState(searchValue);
|
|
524
|
+
const [prevExpandForSearch, setPrevExpandForSearch] =
|
|
525
|
+
React.useState(expandForSearch);
|
|
526
|
+
|
|
527
|
+
// Re-sync when the query changes or when newly resolved data turns this
|
|
528
|
+
// branch into a match under the same query (deferred subtrees resolve
|
|
529
|
+
// asynchronously, so the decision can flip without the query changing).
|
|
530
|
+
if (
|
|
531
|
+
prevSearchValue !== searchValue ||
|
|
532
|
+
prevExpandForSearch !== expandForSearch
|
|
533
|
+
) {
|
|
534
|
+
setPrevSearchValue(searchValue);
|
|
535
|
+
setPrevExpandForSearch(expandForSearch);
|
|
536
|
+
setIsExpanded(expandForSearch);
|
|
528
537
|
}
|
|
529
538
|
|
|
530
539
|
return (
|
|
@@ -657,12 +666,30 @@ interface SchemaNodeProps {
|
|
|
657
666
|
const SchemaNode: React.FC<SchemaNodeProps> = (props) => {
|
|
658
667
|
const { schema, schemaPath, depth } = props;
|
|
659
668
|
const tree = useDataSourceTree();
|
|
660
|
-
const { databaseName,
|
|
661
|
-
|
|
669
|
+
const { databaseName, searchValue } = tree;
|
|
670
|
+
// Auto-expand during search only when this schema's loaded subtree contains a
|
|
671
|
+
// matching table.
|
|
672
|
+
const expandForSearch = schemaSubtreeMatchesSearch(schema, searchValue);
|
|
673
|
+
const [isExpanded, setIsExpanded] = React.useState(expandForSearch);
|
|
662
674
|
const [isSelected, setIsSelected] = React.useState(false);
|
|
675
|
+
const [prevSearchValue, setPrevSearchValue] = React.useState(searchValue);
|
|
676
|
+
const [prevExpandForSearch, setPrevExpandForSearch] =
|
|
677
|
+
React.useState(expandForSearch);
|
|
663
678
|
const uniqueValue = `${databaseName}:${schemaPath.join(".")}`;
|
|
664
679
|
const childSchemas = schema.child_schemas ?? [];
|
|
665
680
|
|
|
681
|
+
// Re-sync when the query changes or when newly resolved data turns this
|
|
682
|
+
// subtree into a match under the same query (deferred tables/child schemas
|
|
683
|
+
// resolve asynchronously, so the decision can flip without the query changing).
|
|
684
|
+
if (
|
|
685
|
+
prevSearchValue !== searchValue ||
|
|
686
|
+
prevExpandForSearch !== expandForSearch
|
|
687
|
+
) {
|
|
688
|
+
setPrevSearchValue(searchValue);
|
|
689
|
+
setPrevExpandForSearch(expandForSearch);
|
|
690
|
+
setIsExpanded(expandForSearch);
|
|
691
|
+
}
|
|
692
|
+
|
|
666
693
|
return (
|
|
667
694
|
<>
|
|
668
695
|
<CommandItem
|
|
@@ -52,6 +52,51 @@ export function areChildSchemasResolved(schema: DatabaseSchema): boolean {
|
|
|
52
52
|
return schema.child_schemas_resolved !== false;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Whether a loaded schema subtree contains a table whose name matches
|
|
57
|
+
* `searchValue`. Deferred (not-yet-fetched) tables and child schemas are
|
|
58
|
+
* treated as non-matching, so searching never triggers a lazy catalog fetch on
|
|
59
|
+
* large/remote connections.
|
|
60
|
+
*/
|
|
61
|
+
export function schemaSubtreeMatchesSearch(
|
|
62
|
+
schema: DatabaseSchema,
|
|
63
|
+
searchValue: string | undefined,
|
|
64
|
+
): boolean {
|
|
65
|
+
const query = searchValue?.trim().toLowerCase();
|
|
66
|
+
if (!query) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
areTablesResolved(schema) &&
|
|
71
|
+
schema.tables.some((table) => table.name.toLowerCase().includes(query))
|
|
72
|
+
) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
if (areChildSchemasResolved(schema)) {
|
|
76
|
+
return (schema.child_schemas ?? []).some((child) =>
|
|
77
|
+
schemaSubtreeMatchesSearch(child, query),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Auto-expand a database row during search only when one of its loaded schemas
|
|
85
|
+
* contains a matching table. Returns false when the schema list itself is
|
|
86
|
+
* deferred.
|
|
87
|
+
*/
|
|
88
|
+
export function shouldExpandDatabaseForSearch(
|
|
89
|
+
database: Database,
|
|
90
|
+
searchValue: string | undefined,
|
|
91
|
+
): boolean {
|
|
92
|
+
if (!searchValue?.trim() || !areSchemasResolved(database)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
return database.schemas.some((schema) =>
|
|
96
|
+
schemaSubtreeMatchesSearch(schema, searchValue),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
55
100
|
interface SqlCodeFormatter {
|
|
56
101
|
/**
|
|
57
102
|
* Format the table path based on dialect-specific rules
|
|
@@ -45,7 +45,12 @@ export const DEFAULT_AI_MODEL = "openai/gpt-4o";
|
|
|
45
45
|
const AUTO_DOWNLOAD_FORMATS = ["html", "markdown", "ipynb"] as const;
|
|
46
46
|
|
|
47
47
|
export type CopilotMode = NonNullable<schemas["AiConfig"]["mode"]>;
|
|
48
|
-
export const COPILOT_MODES: CopilotMode[] = [
|
|
48
|
+
export const COPILOT_MODES: CopilotMode[] = [
|
|
49
|
+
"manual",
|
|
50
|
+
"ask",
|
|
51
|
+
"agent",
|
|
52
|
+
"code_mode",
|
|
53
|
+
];
|
|
49
54
|
|
|
50
55
|
const AiConfigSchema = z
|
|
51
56
|
.object({
|
package/src/css/md.css
CHANGED
|
@@ -49,6 +49,18 @@
|
|
|
49
49
|
.markdown h6 {
|
|
50
50
|
font-weight: inherit;
|
|
51
51
|
font-family: var(--heading-font);
|
|
52
|
+
|
|
53
|
+
/*
|
|
54
|
+
Anchor heading spacing to GitHub's markdown rhythm (Primer): a uniform
|
|
55
|
+
24px top / 16px bottom margin and 1.25 line-height across every level,
|
|
56
|
+
letting font-size — not margin — carry the hierarchy. Tailwind Typography's
|
|
57
|
+
prose defaults scale heading margins per-level up to ~2em (≈48px on h2),
|
|
58
|
+
which is tuned for long-form articles and reads as too airy in a notebook.
|
|
59
|
+
Ref: https://github.com/sindresorhus/github-markdown-css
|
|
60
|
+
*/
|
|
61
|
+
margin-top: 1.5rem;
|
|
62
|
+
margin-bottom: 1rem;
|
|
63
|
+
line-height: 1.25;
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
.markdown hr {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./worker-CC0Oul9k.js";var t=e(((e,t)=>{t.exports={}}));export default t();
|