gentle-shell-bridge 0.3.0 → 0.3.2

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 (76) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +91 -722
  3. package/docs/contract.md +92 -35
  4. package/package.json +13 -1
  5. package/src/api.ts +1 -1
  6. package/.codegraph/.gitignore +0 -5
  7. package/PRD.md +0 -144
  8. package/examples/bash-card.ts +0 -1694
  9. package/gentle-shell-bridge-0.3.0.tgz +0 -0
  10. package/odd/tasks/palette-sections.md +0 -30
  11. package/odd/tasks/plugin-keymap.md +0 -32
  12. package/odd/tasks/plugin-surface-contract.md +0 -55
  13. package/test/api.test.ts +0 -395
  14. package/test/apply-on-move.test.ts +0 -477
  15. package/test/arm.test.ts +0 -220
  16. package/test/bridge-ready.test.ts +0 -309
  17. package/test/bus.test.ts +0 -165
  18. package/test/button-rows.test.ts +0 -409
  19. package/test/capture.test.ts +0 -356
  20. package/test/card-style-rows.test.ts +0 -510
  21. package/test/card-style-transform.test.ts +0 -379
  22. package/test/card-style-wiring.test.ts +0 -258
  23. package/test/card-styles-e2e.test.ts +0 -491
  24. package/test/card-styles.test.ts +0 -324
  25. package/test/card-transcript-path.test.ts +0 -178
  26. package/test/config.test.ts +0 -93
  27. package/test/e2e.test.ts +0 -443
  28. package/test/example-bash-identity.test.ts +0 -372
  29. package/test/example-bash-matrix.test.ts +0 -856
  30. package/test/example-designs.test.ts +0 -734
  31. package/test/example-neon-transform.test.ts +0 -283
  32. package/test/example-plain-style.test.ts +0 -1268
  33. package/test/extension.test.ts +0 -160
  34. package/test/fake-events.ts +0 -58
  35. package/test/fake-host.ts +0 -211
  36. package/test/fake-sidebar.ts +0 -516
  37. package/test/fake-transcript.ts +0 -486
  38. package/test/frame-telemetry.test.ts +0 -417
  39. package/test/hook.test.ts +0 -196
  40. package/test/manifest.test.ts +0 -105
  41. package/test/new-category.test.ts +0 -979
  42. package/test/notices.test.ts +0 -328
  43. package/test/plugin-integration-lines.test.ts +0 -979
  44. package/test/plugin-surface.test.ts +0 -152
  45. package/test/presence.test.ts +0 -459
  46. package/test/rail-surfaces.test.ts +0 -457
  47. package/test/rail-width.test.ts +0 -423
  48. package/test/regression.test.ts +0 -483
  49. package/test/repaint-api.test.ts +0 -200
  50. package/test/reserved-categories.test.ts +0 -361
  51. package/test/row-labels.test.ts +0 -284
  52. package/test/row-names.test.ts +0 -473
  53. package/test/row-origin.test.ts +0 -395
  54. package/test/section-interaction.test.ts +0 -364
  55. package/test/section-panel.test.ts +0 -403
  56. package/test/sections-rows.test.ts +0 -291
  57. package/test/sidebar-backend.test.ts +0 -471
  58. package/test/sidebar-e2e.test.ts +0 -331
  59. package/test/sidebar-hover.test.ts +0 -276
  60. package/test/sidebar-independent.test.ts +0 -349
  61. package/test/sidebar-render.test.ts +0 -281
  62. package/test/sidebar-sections.test.ts +0 -245
  63. package/test/status-card-styles.test.ts +0 -201
  64. package/test/status-section-frame.test.ts +0 -181
  65. package/test/status-sections.test.ts +0 -243
  66. package/test/status.test.ts +0 -209
  67. package/test/store.test.ts +0 -192
  68. package/test/surface-repaint-cost.test.ts +0 -520
  69. package/test/tool-design-chain.test.ts +0 -483
  70. package/test/tool-design-cost.test.ts +0 -473
  71. package/test/tool-design-rows.test.ts +0 -359
  72. package/test/tool-designs.test.ts +0 -745
  73. package/test/transform-pipeline.test.ts +0 -465
  74. package/test/transforms-bus.test.ts +0 -233
  75. package/test/verifier-width-regressions.test.ts +0 -164
  76. package/tsconfig.json +0 -18
@@ -1,164 +0,0 @@
1
- /**
2
- * The verifier's width findings, pinned (W2-T2–W2-T4, A-T5, C-T2/C-T3 adjacent).
3
- *
4
- * Each expectation below is the value pi-tui's own `visibleWidth` produces for that string
5
- * (`@earendil-works/pi-tui/dist/utils.js:126-268`, measured with the installed host copy), so this
6
- * file is a differential fixture with pinned numbers instead of a second implementation:
7
- *
8
- * - a cluster is charged the way `graphemeWidth` charges it: zero for a cluster of marks, two only
9
- * when `couldBeEmoji` AND `\p{RGI_Emoji}` both hold (so `©` is ONE column, `©️` is two), the base
10
- * code point's East Asian width otherwise, plus every trailing halfwidth/fullwidth form and the
11
- * Thai/Lao AM vowels;
12
- * - a row that already fits is byte-identical, escapes included, even at width zero;
13
- * - a cut never splits a grapheme cluster, even when an SGR sequence sits inside it.
14
- *
15
- * The README assertion at the end pins the exact footer string the host prints for a bridge row
16
- * (A5).
17
- */
18
-
19
- import assert from "node:assert/strict";
20
- import { readFileSync } from "node:fs";
21
- import test from "node:test";
22
-
23
- import { registerTransform, resetTransformsForTests } from "../src/bus.ts";
24
- import { clipToWidth, hostColumns } from "../src/card-style-transform.ts";
25
- import { applyTransforms, resetTransformPipelineForTests } from "../src/transform-pipeline.ts";
26
-
27
- test.beforeEach(() => {
28
- resetTransformsForTests();
29
- resetTransformPipelineForTests();
30
- });
31
-
32
- // ---------------------------------------------------------------------------------------------
33
- // The host's measure, cluster by cluster
34
- // ---------------------------------------------------------------------------------------------
35
-
36
- test("a symbol that is not an RGI emoji is one column; a qualified or keycap emoji is two", () => {
37
- for (const symbol of ["©", "®", "™", "☺"]) {
38
- assert.equal(hostColumns(symbol), 1, `${symbol} is not an RGI emoji: the host charges one column`);
39
- }
40
- assert.equal(hostColumns("©️"), 2, "the emoji presentation selector makes it an RGI emoji");
41
- assert.equal(hostColumns("1️⃣"), 2, "a keycap sequence is one two-column cluster");
42
- });
43
-
44
- test("a trailing halfwidth/fullwidth form is charged its own column", () => {
45
- assert.equal(hostColumns("ガ"), 2, "halfwidth KA plus the voiced sound mark");
46
- assert.equal(hostColumns("ガギ"), 4, "two voiced halfwidth kana are four columns");
47
- assert.equal(hostColumns("ア"), 1, "a bare halfwidth kana is one column");
48
- });
49
-
50
- test("a cluster of marks charges nothing, and a leading mark rides its base", () => {
51
- assert.equal(hostColumns("्"), 0, "a lone Devanagari virama occupies no cell");
52
- assert.equal(hostColumns("़क"), 1, "the leading nukta is stripped before measuring क");
53
- assert.equal(hostColumns("क्"), 1);
54
- assert.equal(hostColumns("e\u0301"), 1, "a combining acute rides its base");
55
- });
56
-
57
- test("a Devanagari spacing mark occupies its own cell, as the host allocates one", () => {
58
- assert.equal(hostColumns("ि"), 1, "U+093F is a Spacing_Mark: the host charges it one column");
59
- assert.equal(hostColumns("ा"), 1, "U+093E is a Spacing_Mark too");
60
- assert.equal(hostColumns("नमस्ते"), 4, "the host's own number for नमस्ते (pi-tui 1.5.x)");
61
- assert.equal(hostColumns("हिन्दी"), 5, "the host's own number for हिन्दी");
62
- });
63
-
64
- // ---------------------------------------------------------------------------------------------
65
- // W2-3: a consonant that follows a mark is charged, so it cannot overflow a narrower frame
66
- // ---------------------------------------------------------------------------------------------
67
-
68
- test("an Indic conjunct that follows a mark is two columns, and is never cut into one column", () => {
69
- // pi-tui 1.5.x charges the base (क) plus the visible consonant after the virama (ष): 2 columns.
70
- // Charging the cluster as its base alone made a "one column" frame hold two.
71
- assert.equal(hostColumns("क्‍ष"), 2, "the host charges the conjunct two columns");
72
- assert.equal(hostColumns("क्ष"), 2, "with or without the joiner");
73
- assert.equal(hostColumns("क्‍षा"), 3, "the trailing spacing mark adds its own cell");
74
-
75
- assert.equal(
76
- clipToWidth("क्‍षx", 1),
77
- "",
78
- "a two-column cluster cannot be emitted in a one-column frame",
79
- );
80
- assert.equal(clipToWidth("क्‍षx", 2), "क्‍ष", "at two columns the whole cluster fits");
81
- assert.equal(hostColumns(clipToWidth("क्‍षx", 2)), 2, "and the result measures exactly two");
82
- for (const width of [0, 1, 2, 3]) {
83
- assert.ok(
84
- hostColumns(clipToWidth("क्‍षx", width)) <= width,
85
- `the cut at ${width} never exceeds the frame`,
86
- );
87
- }
88
- });
89
-
90
- test("the pipeline clips an overflowing Indic conjunct instead of emitting it", () => {
91
- registerTransform({ id: "indic", transform: () => ["क्‍षx"] });
92
-
93
- assert.deepEqual(applyTransforms(["host"], 1), [""], "nothing wider than the frame reaches the host");
94
- assert.deepEqual(applyTransforms(["host"], 2), ["क्‍ष"]);
95
- });
96
-
97
- test("a Thai/Lao AM vowel adds a column, and an ignorable symbol adds none", () => {
98
- assert.equal(hostColumns("กำ"), 2, "the trailing AM vowel is charged one extra column");
99
- assert.equal(hostColumns("ເກຳ"), 3, "the same rule for Lao");
100
- assert.equal(hostColumns("\u00ad"), 0, "a soft hyphen is default-ignorable: no columns");
101
- assert.equal(hostColumns("👨‍👩‍👧‍👦"), 2, "a ZWJ family is one two-column cluster");
102
- assert.equal(hostColumns("👍🏽"), 2, "a skin-tone modifier rides its base");
103
- });
104
-
105
- // ---------------------------------------------------------------------------------------------
106
- // Fitting rows are byte-identical, at every width — including zero
107
- // ---------------------------------------------------------------------------------------------
108
-
109
- test("a row that fits at width zero keeps its bytes, escapes included", () => {
110
- const sgr = "\u001b[31m";
111
- const link = "\u001b]8;;http://example.com\u0007\u001b]8;;\u0007";
112
- const background = "\u001b[48;5;236m\u001b[0m";
113
-
114
- assert.equal(clipToWidth(sgr, 0), sgr, "an SGR-only row has no columns and must survive");
115
- assert.equal(clipToWidth(link, 0), link, "an empty hyperlink is the same empty hyperlink");
116
- assert.equal(clipToWidth(background, 0), background);
117
- assert.equal(clipToWidth("\u001b[31m\u001b[39m", 0), "\u001b[31m\u001b[39m");
118
- });
119
-
120
- test("a row with no columns is passed through, and a row with columns is emptied, at width zero", () => {
121
- assert.equal(clipToWidth("", 0), "");
122
- assert.equal(clipToWidth("A", 0), "", "a visible column cannot be drawn in zero columns");
123
- assert.equal(clipToWidth("\u001b[31mA", 0), "", "the paint alone is not a row fit to draw");
124
- });
125
-
126
- test("the pipeline returns an ANSI-only row untouched at width zero", () => {
127
- registerTransform({ id: "ansi-only", transform: () => ["\u001b[31m", "A"] });
128
-
129
- assert.deepEqual(applyTransforms(["host"], 0), ["\u001b[31m", ""], "only the visible column is cut");
130
- });
131
-
132
- // ---------------------------------------------------------------------------------------------
133
- // The cut never splits a cluster, even with SGR interleaved
134
- // ---------------------------------------------------------------------------------------------
135
-
136
- test("an SGR inside a grapheme cluster does not split it, and the kept paint is reset", () => {
137
- const row = "👨\u001b[31m‍👩‍👧‍👦AB";
138
- assert.equal(hostColumns(row), 4, "the host measures the family as one cluster plus A and B");
139
-
140
- const clipped = clipToWidth(row, 2);
141
-
142
- assert.equal(clipped, "👨\u001b[31m‍👩‍👧‍👦\u001b[0m", "the whole family is kept, then the paint is reset");
143
- assert.equal(clipped.split("\u200d").length - 1, 3, "every joiner of the family survived the cut");
144
- assert.equal(hostColumns(clipped), 2, "and it measures the host's width exactly");
145
- });
146
-
147
- test("the pipeline applies that cut to what a transform returns", () => {
148
- registerTransform({ id: "family", transform: () => ["👨\u001b[31m‍👩‍👧‍👦AB"] });
149
-
150
- assert.deepEqual(applyTransforms(["host"], 2), ["👨\u001b[31m‍👩‍👧‍👦\u001b[0m"]);
151
- });
152
-
153
- // ---------------------------------------------------------------------------------------------
154
- // A5 — the README states the host's exact footer
155
- // ---------------------------------------------------------------------------------------------
156
-
157
- test("the README states the host's exact footer for a focused bridge row (A5)", () => {
158
- const readme = readFileSync(new URL("../README.md", import.meta.url), "utf8");
159
-
160
- assert.ok(
161
- readme.includes("←/→ choose · Enter apply"),
162
- "the README must name the footer verbatim, as the host prints it",
163
- );
164
- });
package/tsconfig.json DELETED
@@ -1,18 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2024",
4
- "lib": ["ES2024"],
5
- "module": "nodenext",
6
- "moduleResolution": "nodenext",
7
- "types": ["node"],
8
- "strict": true,
9
- "noEmit": true,
10
- "allowImportingTsExtensions": true,
11
- "erasableSyntaxOnly": true,
12
- "verbatimModuleSyntax": true,
13
- "isolatedModules": true,
14
- "forceConsistentCasingInFileNames": true,
15
- "skipLibCheck": true
16
- },
17
- "include": ["src/**/*.ts", "test/**/*.ts"]
18
- }