Skip to content

Commit 147ebc9

Browse files
committed
fix(notes): keep the note stylesheet in the production bundle
`NotesWindow.module.css` was imported for its side effect only, with no binding. Rollup tree-shakes a binding-less CSS-module import out of the production bundle, so the shipped app carried zero `.tiptap` rules — dev looked fine because Vite injects module CSS at runtime there. Without `height: 100%` + `overflow-y: auto` the note body stops being a scroll container and grows to the height of its content. Paste a long note and ProseMirror scrolls the caret into view, which now scrolls the `overflow: hidden` shell instead: the toolbar leaves the viewport and the wheel cannot bring it back. Measured on a 400x540 window with ~5.8k characters pasted — editor 6360px tall, `scrollTop` pinned, toolbar at y=-5913, zero buttons hit-testable. Every selector in the file was already `:global`, so the CSS module bought nothing: rename it to plain `.css` and drop the wrappers. A plain CSS import is always emitted. This also restores the mirror transform, list markers, code blocks and heading sizes in production.
1 parent 1749d84 commit 147ebc9

3 files changed

Lines changed: 62 additions & 33 deletions

File tree

src/components/launch/NotesWindow.module.css renamed to src/components/launch/NotesWindow.css

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
/* Tiptap sets class="tiptap" on the ProseMirror root — must be :global to escape CSS modules. */
2-
:global(.tiptap) {
1+
/* Tiptap sets class="tiptap" on the ProseMirror root, so every selector here is global.
2+
* Plain .css, NOT .module.css: this sheet is imported for its side effect only, and a
3+
* binding-less CSS-module import is tree-shaken out of the production bundle. */
4+
.tiptap {
35
height: 100%;
46
width: 100%;
57
outline: none;
@@ -8,11 +10,11 @@
810
caret-color: #111827;
911
}
1012

11-
:global(.notes-teleprompter-content[data-mirrored="true"]) {
13+
.notes-teleprompter-content[data-mirrored="true"] {
1214
transform: scaleX(-1);
1315
}
1416

15-
:global(.tiptap) :first-child {
17+
.tiptap :first-child {
1618
margin-top: 0;
1719
}
1820

@@ -21,38 +23,38 @@
2123
* container. Every element below inherits that size, so `em` here equals the container size
2224
* and the default 16px rendering is unchanged. Headings are the exception — see below.
2325
*/
24-
:global(.tiptap) p {
26+
.tiptap p {
2527
min-height: 1.5em;
2628
line-height: 1.5;
2729
margin-top: 0.75em;
2830
margin-bottom: 0.75em;
2931
}
3032

31-
:global(.tiptap) p:first-child {
33+
.tiptap p:first-child {
3234
margin-top: 0;
3335
}
3436

3537
/* Tailwind preflight strips list markers — restore them for markdown-style input rules. */
36-
:global(.tiptap) ul,
37-
:global(.tiptap) ol {
38+
.tiptap ul,
39+
.tiptap ol {
3840
padding-left: 1.5em;
3941
margin: 1em 0;
4042
}
4143

42-
:global(.tiptap) ul {
44+
.tiptap ul {
4345
list-style-type: disc;
4446
}
4547

46-
:global(.tiptap) ol {
48+
.tiptap ol {
4749
list-style-type: decimal;
4850
}
4951

50-
:global(.tiptap) li {
52+
.tiptap li {
5153
display: list-item;
5254
}
5355

54-
:global(.tiptap) ul li p,
55-
:global(.tiptap) ol li p {
56+
.tiptap ul li p,
57+
.tiptap ol li p {
5658
margin-top: 0.25em;
5759
margin-bottom: 0.25em;
5860
}
@@ -62,50 +64,50 @@
6264
* enlarged font size, not the container's, so switching them would change the default
6365
* rendering rather than just scale it.
6466
*/
65-
:global(.tiptap) h1,
66-
:global(.tiptap) h2,
67-
:global(.tiptap) h3,
68-
:global(.tiptap) h4,
69-
:global(.tiptap) h5,
70-
:global(.tiptap) h6 {
67+
.tiptap h1,
68+
.tiptap h2,
69+
.tiptap h3,
70+
.tiptap h4,
71+
.tiptap h5,
72+
.tiptap h6 {
7173
line-height: 1.1;
7274
margin-top: 2.5rem;
7375
text-wrap: pretty;
7476
}
7577

76-
:global(.tiptap) h1,
77-
:global(.tiptap) h2 {
78+
.tiptap h1,
79+
.tiptap h2 {
7880
margin-top: 3.5rem;
7981
margin-bottom: 1.5rem;
8082
}
8183

82-
:global(.tiptap) h1 {
84+
.tiptap h1 {
8385
font-size: 1.4em;
8486
}
8587

86-
:global(.tiptap) h2 {
88+
.tiptap h2 {
8789
font-size: 1.2em;
8890
}
8991

90-
:global(.tiptap) h3 {
92+
.tiptap h3 {
9193
font-size: 1.1em;
9294
}
9395

94-
:global(.tiptap) h4,
95-
:global(.tiptap) h5,
96-
:global(.tiptap) h6 {
96+
.tiptap h4,
97+
.tiptap h5,
98+
.tiptap h6 {
9799
font-size: 1em;
98100
}
99101

100-
:global(.tiptap) code {
102+
.tiptap code {
101103
background-color: #ede9fe;
102104
border-radius: 0.4rem;
103105
color: #111827;
104106
font-size: 0.85em;
105107
padding: 0.25em 0.3em;
106108
}
107109

108-
:global(.tiptap) pre {
110+
.tiptap pre {
109111
background: #111827;
110112
border-radius: 0.5rem;
111113
color: #ffffff;
@@ -114,20 +116,20 @@
114116
padding: 0.75em 1em;
115117
}
116118

117-
:global(.tiptap) pre code {
119+
.tiptap pre code {
118120
background: none;
119121
color: inherit;
120122
font-size: 0.8em;
121123
padding: 0;
122124
}
123125

124-
:global(.tiptap) blockquote {
126+
.tiptap blockquote {
125127
border-left: 3px solid #d1d5db;
126128
margin: 1.5em 0;
127129
padding-left: 1em;
128130
}
129131

130-
:global(.tiptap) hr {
132+
.tiptap hr {
131133
border: none;
132134
border-top: 1px solid #e5e7eb;
133135
margin: 2em 0;

src/components/launch/NotesWindow.test.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import "@testing-library/jest-dom";
2+
import { readFileSync } from "node:fs";
3+
import { resolve } from "node:path";
24
import { act, render, screen, waitFor } from "@testing-library/react";
35
import userEvent from "@testing-library/user-event";
46
import type { Editor } from "@tiptap/react";
@@ -395,3 +397,28 @@ describe("NotesWindow teleprompter mode", () => {
395397
expect(localStorage.getItem("notes")).toBe("<p>Updated</p>");
396398
});
397399
});
400+
401+
describe("NotesWindow stylesheet", () => {
402+
// The note body only scrolls because `.tiptap` carries `height: 100%` + `overflow-y: auto`.
403+
// Those rules reach the app through a side-effect import, and a side-effect import of a
404+
// *CSS module* is tree-shaken out of the production bundle — dev looked fine while the
405+
// packaged app let a long note grow past its slot, scroll the whole shell out of view and
406+
// take the toolbar with it. A plain `.css` import is always emitted.
407+
const read = (file: string) =>
408+
readFileSync(resolve(process.cwd(), "src/components/launch", file), "utf8");
409+
410+
it("is imported as plain CSS, never as a CSS module", () => {
411+
const source = read("NotesWindow.tsx");
412+
413+
expect(source).toContain('import "./NotesWindow.css"');
414+
expect(source).not.toContain(".module.css");
415+
});
416+
417+
it("keeps the note body a scroll container", () => {
418+
const css = read("NotesWindow.css");
419+
const body = css.match(/\.tiptap\s*\{[^}]*\}/)?.[0] ?? "";
420+
421+
expect(body).toMatch(/height:\s*100%/);
422+
expect(body).toMatch(/overflow-y:\s*auto/);
423+
});
424+
});

src/components/launch/NotesWindow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
saveNotesTeleprompterSettings,
1818
TELEPROMPTER_SPEED_STEP,
1919
} from "./notesTeleprompter";
20-
import "./NotesWindow.module.css";
20+
import "./NotesWindow.css";
2121

2222
export function NotesWindow() {
2323
const [settings, setSettings] = useState(loadNotesTeleprompterSettings);

0 commit comments

Comments
 (0)