@mindlogic-ai/logician-ui 5.0.0-alpha.20 → 5.0.0-alpha.21

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.
@@ -1,4 +1,6 @@
1
1
  import type { BundledLanguage } from 'shiki';
2
2
  export declare const BUNDLED_LANGUAGES: BundledLanguage[];
3
- export declare const shikiAdapter: import("@chakra-ui/react").CodeBlockAdapter;
3
+ declare const baseAdapter: import("@chakra-ui/react").CodeBlockAdapter;
4
+ export declare const shikiAdapter: typeof baseAdapter;
5
+ export {};
4
6
  //# sourceMappingURL=shikiAdapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shikiAdapter.d.ts","sourceRoot":"","sources":["../../../src/components/Code/shikiAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAoC,MAAM,OAAO,CAAC;AAE/E,eAAO,MAAM,iBAAiB,EAAE,eAAe,EA8B9C,CAAC;AAEF,eAAO,MAAM,YAAY,6CAWvB,CAAC"}
1
+ {"version":3,"file":"shikiAdapter.d.ts","sourceRoot":"","sources":["../../../src/components/Code/shikiAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAoC,MAAM,OAAO,CAAC;AAE/E,eAAO,MAAM,iBAAiB,EAAE,eAAe,EA8B9C,CAAC;AA+BF,QAAA,MAAM,WAAW,6CAGf,CAAC;AAKH,eAAO,MAAM,YAAY,EAAE,OAAO,WAGjC,CAAC"}
@@ -34,16 +34,37 @@ const BUNDLED_LANGUAGES = [
34
34
  'xml',
35
35
  'yaml',
36
36
  ];
37
- const shikiAdapter = react.createShikiAdapter({
38
- async load() {
39
- const { createHighlighter } = await Promise.resolve().then(function () { return require('../../node_modules/shiki/dist/index.js'); });
40
- return createHighlighter({
41
- langs: BUNDLED_LANGUAGES,
42
- themes: ['github-dark'],
43
- });
44
- },
37
+ // One highlighter for the whole app. Every <Code> mounts its own
38
+ // AdapterProvider, and Chakra calls `load()` once per provider. Without this
39
+ // cache a chat with 136 code blocks built 136 highlighters, each compiling all
40
+ // BUNDLED_LANGUAGES, and froze the tab for tens of seconds.
41
+ let highlighterPromise = null;
42
+ const loadHighlighter = () => {
43
+ highlighterPromise ?? (highlighterPromise = Promise.resolve().then(function () { return require('../../node_modules/shiki/dist/index.js'); })
44
+ .then(({ createHighlighter }) => createHighlighter({
45
+ langs: BUNDLED_LANGUAGES,
46
+ themes: ['github-dark'],
47
+ }))
48
+ .catch((error) => {
49
+ // Chakra does not catch a rejected load, so resolve to null instead: the
50
+ // block renders as plain text. Clear the cache so the next mount retries.
51
+ console.error('[logician-ui] Failed to load the Shiki highlighter', error);
52
+ highlighterPromise = null;
53
+ return null;
54
+ }));
55
+ return highlighterPromise;
56
+ };
57
+ const baseAdapter = react.createShikiAdapter({
58
+ load: loadHighlighter,
45
59
  theme: 'github-dark',
46
60
  });
61
+ // Chakra's default `unloadContext` calls `dispose()` when a provider unmounts.
62
+ // The highlighter is shared, so one block unmounting would break highlighting
63
+ // in every other block. Keep it alive for the life of the page.
64
+ const shikiAdapter = {
65
+ ...baseAdapter,
66
+ unloadContext: () => { },
67
+ };
47
68
 
48
69
  exports.BUNDLED_LANGUAGES = BUNDLED_LANGUAGES;
49
70
  exports.shikiAdapter = shikiAdapter;
@@ -1 +1 @@
1
- {"version":3,"file":"shikiAdapter.js","sources":["../../../src/components/Code/shikiAdapter.ts"],"sourcesContent":[null],"names":["createShikiAdapter"],"mappings":";;;;;AAGO,MAAM,iBAAiB,GAAsB;IAClD,MAAM;IACN,GAAG;IACH,QAAQ;IACR,KAAK;IACL,MAAM;IACN,YAAY;IACZ,IAAI;IACJ,MAAM;IACN,MAAM;IACN,YAAY;IACZ,MAAM;IACN,KAAK;IACL,QAAQ;IACR,KAAK;IACL,UAAU;IACV,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,GAAG;IACH,MAAM;IACN,MAAM;IACN,OAAO;IACP,KAAK;IACL,OAAO;IACP,MAAM;IACN,KAAK;IACL,YAAY;IACZ,KAAK;IACL,MAAM;;AAGD,MAAM,YAAY,GAAGA,wBAAkB,CAE5C;AACA,IAAA,MAAM,IAAI,GAAA;QACR,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,oDAAO,wCAAO,KAAC;AACnD,QAAA,OAAO,iBAAiB,CAAC;AACvB,YAAA,KAAK,EAAE,iBAAiB;YACxB,MAAM,EAAE,CAAC,aAAa,CAAC;AACxB,SAAA,CAAC;IACJ,CAAC;AACD,IAAA,KAAK,EAAE,aAAa;AACrB,CAAA;;;;;"}
1
+ {"version":3,"file":"shikiAdapter.js","sources":["../../../src/components/Code/shikiAdapter.ts"],"sourcesContent":[null],"names":["createShikiAdapter"],"mappings":";;;;;AAGO,MAAM,iBAAiB,GAAsB;IAClD,MAAM;IACN,GAAG;IACH,QAAQ;IACR,KAAK;IACL,MAAM;IACN,YAAY;IACZ,IAAI;IACJ,MAAM;IACN,MAAM;IACN,YAAY;IACZ,MAAM;IACN,KAAK;IACL,QAAQ;IACR,KAAK;IACL,UAAU;IACV,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,GAAG;IACH,MAAM;IACN,MAAM;IACN,OAAO;IACP,KAAK;IACL,OAAO;IACP,MAAM;IACN,KAAK;IACL,YAAY;IACZ,KAAK;IACL,MAAM;;AAKR;AACA;AACA;AACA;AACA,IAAI,kBAAkB,GAAuC,IAAI;AAEjE,MAAM,eAAe,GAAG,MAAkC;AACxD,IAAA,kBAAkB,KAAlB,kBAAkB,GAAK,oDAAO,wCAAO;SAClC,IAAI,CAAC,CAAC,EAAE,iBAAiB,EAAE,KAC1B,iBAAiB,CAAC;AAChB,QAAA,KAAK,EAAE,iBAAiB;QACxB,MAAM,EAAE,CAAC,aAAa,CAAC;AACxB,KAAA,CAAC;AAEH,SAAA,KAAK,CAAC,CAAC,KAAc,KAAI;;;AAGxB,QAAA,OAAO,CAAC,KAAK,CACX,oDAAoD,EACpD,KAAK,CACN;QACD,kBAAkB,GAAG,IAAI;AACzB,QAAA,OAAO,IAAI;AACb,IAAA,CAAC,CAAC,CAAA;AACJ,IAAA,OAAO,kBAAkB;AAC3B,CAAC;AAED,MAAM,WAAW,GAAGA,wBAAkB,CAAqB;AACzD,IAAA,IAAI,EAAE,eAAe;AACrB,IAAA,KAAK,EAAE,aAAa;AACrB,CAAA,CAAC;AAEF;AACA;AACA;AACO,MAAM,YAAY,GAAuB;AAC9C,IAAA,GAAG,WAAW;AACd,IAAA,aAAa,EAAE,MAAK,EAAE,CAAC;;;;;;"}
@@ -32,16 +32,37 @@ const BUNDLED_LANGUAGES = [
32
32
  'xml',
33
33
  'yaml',
34
34
  ];
35
- const shikiAdapter = createShikiAdapter({
36
- async load() {
37
- const { createHighlighter } = await import('../../node_modules/shiki/dist/index.mjs');
38
- return createHighlighter({
39
- langs: BUNDLED_LANGUAGES,
40
- themes: ['github-dark'],
41
- });
42
- },
35
+ // One highlighter for the whole app. Every <Code> mounts its own
36
+ // AdapterProvider, and Chakra calls `load()` once per provider. Without this
37
+ // cache a chat with 136 code blocks built 136 highlighters, each compiling all
38
+ // BUNDLED_LANGUAGES, and froze the tab for tens of seconds.
39
+ let highlighterPromise = null;
40
+ const loadHighlighter = () => {
41
+ highlighterPromise ?? (highlighterPromise = import('../../node_modules/shiki/dist/index.mjs')
42
+ .then(({ createHighlighter }) => createHighlighter({
43
+ langs: BUNDLED_LANGUAGES,
44
+ themes: ['github-dark'],
45
+ }))
46
+ .catch((error) => {
47
+ // Chakra does not catch a rejected load, so resolve to null instead: the
48
+ // block renders as plain text. Clear the cache so the next mount retries.
49
+ console.error('[logician-ui] Failed to load the Shiki highlighter', error);
50
+ highlighterPromise = null;
51
+ return null;
52
+ }));
53
+ return highlighterPromise;
54
+ };
55
+ const baseAdapter = createShikiAdapter({
56
+ load: loadHighlighter,
43
57
  theme: 'github-dark',
44
58
  });
59
+ // Chakra's default `unloadContext` calls `dispose()` when a provider unmounts.
60
+ // The highlighter is shared, so one block unmounting would break highlighting
61
+ // in every other block. Keep it alive for the life of the page.
62
+ const shikiAdapter = {
63
+ ...baseAdapter,
64
+ unloadContext: () => { },
65
+ };
45
66
 
46
67
  export { BUNDLED_LANGUAGES, shikiAdapter };
47
68
  //# sourceMappingURL=shikiAdapter.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"shikiAdapter.mjs","sources":["../../../src/components/Code/shikiAdapter.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAGO,MAAM,iBAAiB,GAAsB;IAClD,MAAM;IACN,GAAG;IACH,QAAQ;IACR,KAAK;IACL,MAAM;IACN,YAAY;IACZ,IAAI;IACJ,MAAM;IACN,MAAM;IACN,YAAY;IACZ,MAAM;IACN,KAAK;IACL,QAAQ;IACR,KAAK;IACL,UAAU;IACV,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,GAAG;IACH,MAAM;IACN,MAAM;IACN,OAAO;IACP,KAAK;IACL,OAAO;IACP,MAAM;IACN,KAAK;IACL,YAAY;IACZ,KAAK;IACL,MAAM;;AAGD,MAAM,YAAY,GAAG,kBAAkB,CAE5C;AACA,IAAA,MAAM,IAAI,GAAA;QACR,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,OAAO,yCAAO,CAAC;AACnD,QAAA,OAAO,iBAAiB,CAAC;AACvB,YAAA,KAAK,EAAE,iBAAiB;YACxB,MAAM,EAAE,CAAC,aAAa,CAAC;AACxB,SAAA,CAAC;IACJ,CAAC;AACD,IAAA,KAAK,EAAE,aAAa;AACrB,CAAA;;;;"}
1
+ {"version":3,"file":"shikiAdapter.mjs","sources":["../../../src/components/Code/shikiAdapter.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAGO,MAAM,iBAAiB,GAAsB;IAClD,MAAM;IACN,GAAG;IACH,QAAQ;IACR,KAAK;IACL,MAAM;IACN,YAAY;IACZ,IAAI;IACJ,MAAM;IACN,MAAM;IACN,YAAY;IACZ,MAAM;IACN,KAAK;IACL,QAAQ;IACR,KAAK;IACL,UAAU;IACV,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,GAAG;IACH,MAAM;IACN,MAAM;IACN,OAAO;IACP,KAAK;IACL,OAAO;IACP,MAAM;IACN,KAAK;IACL,YAAY;IACZ,KAAK;IACL,MAAM;;AAKR;AACA;AACA;AACA;AACA,IAAI,kBAAkB,GAAuC,IAAI;AAEjE,MAAM,eAAe,GAAG,MAAkC;AACxD,IAAA,kBAAkB,KAAlB,kBAAkB,GAAK,OAAO,yCAAO;SAClC,IAAI,CAAC,CAAC,EAAE,iBAAiB,EAAE,KAC1B,iBAAiB,CAAC;AAChB,QAAA,KAAK,EAAE,iBAAiB;QACxB,MAAM,EAAE,CAAC,aAAa,CAAC;AACxB,KAAA,CAAC;AAEH,SAAA,KAAK,CAAC,CAAC,KAAc,KAAI;;;AAGxB,QAAA,OAAO,CAAC,KAAK,CACX,oDAAoD,EACpD,KAAK,CACN;QACD,kBAAkB,GAAG,IAAI;AACzB,QAAA,OAAO,IAAI;AACb,IAAA,CAAC,CAAC,CAAA;AACJ,IAAA,OAAO,kBAAkB;AAC3B,CAAC;AAED,MAAM,WAAW,GAAG,kBAAkB,CAAqB;AACzD,IAAA,IAAI,EAAE,eAAe;AACrB,IAAA,KAAK,EAAE,aAAa;AACrB,CAAA,CAAC;AAEF;AACA;AACA;AACO,MAAM,YAAY,GAAuB;AAC9C,IAAA,GAAG,WAAW;AACd,IAAA,aAAa,EAAE,MAAK,EAAE,CAAC;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindlogic-ai/logician-ui",
3
- "version": "5.0.0-alpha.20",
3
+ "version": "5.0.0-alpha.21",
4
4
  "description": "A comprehensive React design system built on Chakra UI",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -0,0 +1,116 @@
1
+ import { ChakraProvider } from '@chakra-ui/react';
2
+ import { render, waitFor } from '@testing-library/react';
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import { system } from '../../theme';
6
+
7
+ const shiki = vi.hoisted(() => ({
8
+ createHighlighter: vi.fn(),
9
+ dispose: vi.fn(),
10
+ }));
11
+
12
+ // The real Shiki compiles grammars through WASM, which is slow in jsdom and
13
+ // not what these tests are about. The fake marks highlighted output with a
14
+ // class so a test can tell highlighted text from the plain-text fallback.
15
+ vi.mock('shiki', () => ({
16
+ createHighlighter: shiki.createHighlighter,
17
+ }));
18
+
19
+ const fakeHighlighter = {
20
+ codeToHtml: (code: string) =>
21
+ `<pre><code><span class="test-highlighted">${code}</span></code></pre>`,
22
+ dispose: shiki.dispose,
23
+ };
24
+
25
+ // shikiAdapter caches the highlighter at module level. Re-import Code per test
26
+ // so each one starts with an empty cache.
27
+ const loadCode = async () => (await import('./Code')).Code;
28
+
29
+ const wrap = (ui: React.ReactNode) =>
30
+ render(<ChakraProvider value={system}>{ui}</ChakraProvider>);
31
+
32
+ beforeEach(() => {
33
+ vi.resetModules();
34
+ shiki.createHighlighter.mockReset();
35
+ shiki.createHighlighter.mockResolvedValue(fakeHighlighter);
36
+ shiki.dispose.mockReset();
37
+ });
38
+
39
+ describe('Code highlighter', () => {
40
+ it('creates one highlighter for many code blocks', async () => {
41
+ const Code = await loadCode();
42
+ const { container } = wrap(
43
+ <>
44
+ {Array.from({ length: 20 }, (_, i) => (
45
+ <Code key={i} language="javascript">{`const n = ${i};`}</Code>
46
+ ))}
47
+ </>
48
+ );
49
+
50
+ await waitFor(() =>
51
+ expect(container.querySelectorAll('.test-highlighted')).toHaveLength(20)
52
+ );
53
+ expect(shiki.createHighlighter).toHaveBeenCalledTimes(1);
54
+ });
55
+
56
+ it('keeps highlighting the other blocks when one block unmounts', async () => {
57
+ const Code = await loadCode();
58
+ const Blocks = ({ showFirst }: { showFirst: boolean }) => (
59
+ <>
60
+ {showFirst && <Code language="javascript">first</Code>}
61
+ <Code language="javascript">second</Code>
62
+ </>
63
+ );
64
+ const { container, rerender } = wrap(<Blocks showFirst />);
65
+ await waitFor(() =>
66
+ expect(container.querySelectorAll('.test-highlighted')).toHaveLength(2)
67
+ );
68
+
69
+ rerender(
70
+ <ChakraProvider value={system}>
71
+ <Blocks showFirst={false} />
72
+ </ChakraProvider>
73
+ );
74
+
75
+ expect(shiki.dispose).not.toHaveBeenCalled();
76
+ expect(container.querySelectorAll('.test-highlighted')).toHaveLength(1);
77
+
78
+ // A block mounted later reuses the same highlighter.
79
+ rerender(
80
+ <ChakraProvider value={system}>
81
+ <Blocks showFirst />
82
+ </ChakraProvider>
83
+ );
84
+ await waitFor(() =>
85
+ expect(container.querySelectorAll('.test-highlighted')).toHaveLength(2)
86
+ );
87
+ expect(shiki.createHighlighter).toHaveBeenCalledTimes(1);
88
+ });
89
+
90
+ it('retries on the next mount when loading fails', async () => {
91
+ const consoleError = vi
92
+ .spyOn(console, 'error')
93
+ .mockImplementation(() => {});
94
+ shiki.createHighlighter.mockRejectedValueOnce(new Error('load failed'));
95
+ const Code = await loadCode();
96
+
97
+ const first = wrap(<Code language="javascript">a</Code>);
98
+ await waitFor(() =>
99
+ expect(consoleError).toHaveBeenCalledWith(
100
+ expect.stringContaining('Failed to load the Shiki highlighter'),
101
+ expect.any(Error)
102
+ )
103
+ );
104
+ // The failed block falls back to plain text instead of throwing.
105
+ expect(first.container).toHaveTextContent('a');
106
+ expect(first.container.querySelector('.test-highlighted')).toBeNull();
107
+ first.unmount();
108
+
109
+ const { container } = wrap(<Code language="javascript">b</Code>);
110
+ await waitFor(() =>
111
+ expect(container.querySelectorAll('.test-highlighted')).toHaveLength(1)
112
+ );
113
+ expect(shiki.createHighlighter).toHaveBeenCalledTimes(2);
114
+ consoleError.mockRestore();
115
+ });
116
+ });
@@ -33,15 +33,44 @@ export const BUNDLED_LANGUAGES: BundledLanguage[] = [
33
33
  'yaml',
34
34
  ];
35
35
 
36
- export const shikiAdapter = createShikiAdapter<
37
- HighlighterGeneric<BundledLanguage, BundledTheme>
38
- >({
39
- async load() {
40
- const { createHighlighter } = await import('shiki');
41
- return createHighlighter({
42
- langs: BUNDLED_LANGUAGES,
43
- themes: ['github-dark'],
36
+ type Highlighter = HighlighterGeneric<BundledLanguage, BundledTheme>;
37
+
38
+ // One highlighter for the whole app. Every <Code> mounts its own
39
+ // AdapterProvider, and Chakra calls `load()` once per provider. Without this
40
+ // cache a chat with 136 code blocks built 136 highlighters, each compiling all
41
+ // BUNDLED_LANGUAGES, and froze the tab for tens of seconds.
42
+ let highlighterPromise: Promise<Highlighter | null> | null = null;
43
+
44
+ const loadHighlighter = (): Promise<Highlighter | null> => {
45
+ highlighterPromise ??= import('shiki')
46
+ .then(({ createHighlighter }) =>
47
+ createHighlighter({
48
+ langs: BUNDLED_LANGUAGES,
49
+ themes: ['github-dark'],
50
+ })
51
+ )
52
+ .catch((error: unknown) => {
53
+ // Chakra does not catch a rejected load, so resolve to null instead: the
54
+ // block renders as plain text. Clear the cache so the next mount retries.
55
+ console.error(
56
+ '[logician-ui] Failed to load the Shiki highlighter',
57
+ error
58
+ );
59
+ highlighterPromise = null;
60
+ return null;
44
61
  });
45
- },
62
+ return highlighterPromise;
63
+ };
64
+
65
+ const baseAdapter = createShikiAdapter<Highlighter | null>({
66
+ load: loadHighlighter,
46
67
  theme: 'github-dark',
47
68
  });
69
+
70
+ // Chakra's default `unloadContext` calls `dispose()` when a provider unmounts.
71
+ // The highlighter is shared, so one block unmounting would break highlighting
72
+ // in every other block. Keep it alive for the life of the page.
73
+ export const shikiAdapter: typeof baseAdapter = {
74
+ ...baseAdapter,
75
+ unloadContext: () => {},
76
+ };