@humanforest/slidev-theme 0.1.0 → 0.1.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.
- package/debug/layoutDebug.js +70 -0
- package/debug/reviewToggles.js +34 -0
- package/global-top.vue +45 -1
- package/package.json +11 -6
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// task-14-brief.md, requirement 6 — the shared toggle behind the layout-debug (red-line) overlay.
|
|
2
|
+
// Split out from global-top.vue itself so setup/shortcuts.ts can flip it without importing a .vue
|
|
3
|
+
// file into a plain Slidev setup module — the same reasoning backgrounds.js/iconSlugs.js are split
|
|
4
|
+
// from the components that read them.
|
|
5
|
+
//
|
|
6
|
+
// OFF BY DEFAULT, ALWAYS — this is the one property every other guarantee in this module and in
|
|
7
|
+
// global-top.vue is built on top of:
|
|
8
|
+
// - `ref(false)`, never read from anywhere at import time — no localStorage/sessionStorage
|
|
9
|
+
// restore, no query-string read, nothing that could start this ref at `true` on a page this
|
|
10
|
+
// module has never seen interacted with. A fresh page load — which is EXACTLY what `slidev
|
|
11
|
+
// export`'s Playwright run does; it never presses a key or replays a prior session — starts
|
|
12
|
+
// here, at `false`, unconditionally.
|
|
13
|
+
// - No persistence at all. Nothing in this module writes to storage, so there is nothing for a
|
|
14
|
+
// later, unrelated page load (an export tab reusing a browser profile a presenter had toggled
|
|
15
|
+
// debug on in, during a live review) to inherit. The cost is that the toggle does not survive
|
|
16
|
+
// a manual reload — accepted deliberately, because the alternative is a mechanism that could
|
|
17
|
+
// leak state into an export, which is the one outcome requirement 6 names as worse than not
|
|
18
|
+
// having this tool at all.
|
|
19
|
+
//
|
|
20
|
+
// This module does NOT decide whether the overlay is currently SAFE to show — it has no way to
|
|
21
|
+
// know it's running inside an export. global-top.vue is what gates the actual render on
|
|
22
|
+
// `useNav().isPrintMode` as well as this ref, so even a hypothetical future bug that flipped
|
|
23
|
+
// `layoutDebugEnabled` true during an export still could not paint anything — see that file's own
|
|
24
|
+
// comment for the two-guard shape and why neither guard alone is treated as sufficient.
|
|
25
|
+
import { reactive, ref } from 'vue';
|
|
26
|
+
|
|
27
|
+
/** @type {import('vue').Ref<boolean>} */
|
|
28
|
+
export const layoutDebugEnabled = ref(false);
|
|
29
|
+
|
|
30
|
+
/** Flips the overlay. The one write site this ref has — used by setup/shortcuts.ts's 'l' binding.
|
|
31
|
+
* Never called from a layout, a component, or a slide. */
|
|
32
|
+
export function toggleLayoutDebug() {
|
|
33
|
+
layoutDebugEnabled.value = !layoutDebugEnabled.value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
THE FIVE TIERS, individually switchable — one on/off for the whole overlay was not enough once it
|
|
38
|
+
started drawing element boxes and gaps as well as rules. A dense slide carries 40-120 element
|
|
39
|
+
hairlines, which is exactly the texture you want when checking whether a paragraph sits where you
|
|
40
|
+
think and exactly the noise you do not want when checking a split's columns.
|
|
41
|
+
|
|
42
|
+
SAME NO-PERSISTENCE DISCIPLINE as `layoutDebugEnabled`, and for the same reason: a plain object
|
|
43
|
+
with literal defaults, never read from storage or a query string, so an export starts here every
|
|
44
|
+
time. All five start ON: boxing the content is half of what makes this a schematic rather than a
|
|
45
|
+
set of rules, so it is there the first time you open it. `element` is the one to switch off when a
|
|
46
|
+
dense slide gets busy, which is exactly why the switches exist.
|
|
47
|
+
|
|
48
|
+
Not a second nav button and not five more shortcuts: the key already names every tier, so the
|
|
49
|
+
key's own rows are the switches. See global-top.vue.
|
|
50
|
+
*/
|
|
51
|
+
export const layoutDebugTiers = reactive({
|
|
52
|
+
/*
|
|
53
|
+
★ THE GRID WASH IS A TIER LIKE THE REST, and it was the one thing the overlay drew that nothing
|
|
54
|
+
could switch off. It is also the densest thing on screen — a 40px lattice under every other
|
|
55
|
+
line — so the moment a reader wants to check one measurement it is the first thing in the way.
|
|
56
|
+
Jose, doing exactly that: "can you remove the grid for a moment? too many lines i want to see if
|
|
57
|
+
everything is correct".
|
|
58
|
+
*/
|
|
59
|
+
grid: true,
|
|
60
|
+
frame: true,
|
|
61
|
+
region: true,
|
|
62
|
+
gap: true,
|
|
63
|
+
element: true,
|
|
64
|
+
hazard: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/** Flips one tier. Called only from the overlay's own key. */
|
|
68
|
+
export function toggleTier(id) {
|
|
69
|
+
if (id in layoutDebugTiers) layoutDebugTiers[id] = !layoutDebugTiers[id];
|
|
70
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Preview-only overrides for the running chrome, driven from the nav bar.
|
|
2
|
+
//
|
|
3
|
+
// WHAT THIS IS FOR. Reviewing a deck means asking "what does this look like without the
|
|
4
|
+
// confidentiality chip / with the inset frames drawn", and the honest answers were previously "edit
|
|
5
|
+
// the frontmatter and reload" and "remember that the key is `l`". Slidev renders a theme's own
|
|
6
|
+
// `custom-nav-controls.vue` inside its toolbar (@slidev/client/internals/NavControls.vue imports
|
|
7
|
+
// `#slidev/custom-nav-controls`), which is the right home for both: a reviewer's control, sitting
|
|
8
|
+
// with the other reviewer's controls, rather than a frontmatter edit that has to be undone.
|
|
9
|
+
//
|
|
10
|
+
// ★★ AN OVERRIDE, NEVER A SETTING — and specifically never one that can reach an export.
|
|
11
|
+
//
|
|
12
|
+
// These refs only ever SUBTRACT from what the frontmatter already asked for. A slide with no
|
|
13
|
+
// `confidential:` cannot be made to show one here, because that would let a reviewer see a deck the
|
|
14
|
+
// deck does not describe. And every consumer must gate on `isPrintMode` the way global-top.vue
|
|
15
|
+
// already does for the debug overlay: `slidev export` drives a real Playwright browser through the
|
|
16
|
+
// same client, so a toggle left on at export time would otherwise silently ship a PDF missing its
|
|
17
|
+
// own confidentiality marking. That is a worse failure than the inconvenience it saves.
|
|
18
|
+
//
|
|
19
|
+
// The debug overlay's own flag stays in ./layoutDebug.js — it already had a home, a keyboard
|
|
20
|
+
// shortcut and a test, and moving it here to make one tidy module would be churn. This file
|
|
21
|
+
// re-exports it so the nav control has a single import.
|
|
22
|
+
import { ref } from 'vue';
|
|
23
|
+
|
|
24
|
+
export { layoutDebugEnabled, toggleLayoutDebug } from './layoutDebug.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Hide the confidentiality chip in PREVIEW only. False by default, so a fresh load always shows
|
|
28
|
+
* exactly what the frontmatter says.
|
|
29
|
+
*/
|
|
30
|
+
export const confidentialHidden = ref(false);
|
|
31
|
+
|
|
32
|
+
export function toggleConfidential() {
|
|
33
|
+
confidentialHidden.value = !confidentialHidden.value;
|
|
34
|
+
}
|
package/global-top.vue
CHANGED
|
@@ -58,9 +58,53 @@ import { useNav } from '@slidev/client';
|
|
|
58
58
|
import { layoutDebugEnabled, layoutDebugTiers, toggleTier } from './debug/layoutDebug.js';
|
|
59
59
|
import deckTokens from '@humanforest/tokens/deck/deck.json';
|
|
60
60
|
|
|
61
|
-
const { isPrintMode, slides, currentPage } = useNav();
|
|
61
|
+
const { isPrintMode, slides, currentPage, prevRoute, nextRoute } = useNav();
|
|
62
62
|
const show = computed(() => layoutDebugEnabled.value && !isPrintMode.value);
|
|
63
63
|
|
|
64
|
+
/*
|
|
65
|
+
── NEIGHBOUR CHUNK WARM-UP ────────────────────────────────────────────────────────────────────
|
|
66
|
+
★★ NOT PART OF THE OVERLAY. It lives here because Slidev gives a theme exactly one seam that is
|
|
67
|
+
mounted once for the whole deck rather than re-mounted per slide, and this is it — the same
|
|
68
|
+
reason the overlay itself is here. It needs `useNav()`, which is a composable and so cannot run
|
|
69
|
+
from `setup/main.ts` where this would otherwise belong.
|
|
70
|
+
|
|
71
|
+
WHAT IT FIXES. The deck's headmatter sets `preload: false` (scripts/deckSpecimen.ts's
|
|
72
|
+
HEAD_DEFAULTS) because Slidev otherwise mounts every route three seconds after load — measured
|
|
73
|
+
at 245 slides: 33,435 nodes and 1.7s of blocking time. But `preloadRoute()` in
|
|
74
|
+
@slidev/client's internals/SlidesShow.vue gates TWO separate things on that one key:
|
|
75
|
+
|
|
76
|
+
watchEffect(() => { preloadRoute(current); preloadRoute(prev); preloadRoute(next) }) // ← A
|
|
77
|
+
watchEffect(() => setTimeout(() => routes.forEach(preloadRoute), 3000)) // ← B
|
|
78
|
+
|
|
79
|
+
B is the stampede. A is not, and turning off B took A with it. So nothing was fetched ahead any
|
|
80
|
+
more, and a slide's own chunk was pulled on arrival instead.
|
|
81
|
+
|
|
82
|
+
★★ THAT SHOWED AS A WHITE FLASH ON EVERY FORWARD STEP OF THE DEPLOYED DECK, and it did not
|
|
83
|
+
reproduce locally, which is why the comment in HEAD_DEFAULTS priced the trade at "about 30ms" —
|
|
84
|
+
a warm dev server. MEASURED on the gh-pages build instead, stepping onto a cold slide: the
|
|
85
|
+
wrapper lands in the DOM with NO `.slidev-layout` inside it for 278ms while `md-*.js`,
|
|
86
|
+
`LondonMap-*.js` and `LondonMap-*.css` are in flight. An empty slide paints Slidev's own
|
|
87
|
+
`bg-main` on `#slide-content` (internals/SlideContainer.vue), which is white. `SlideLoading`
|
|
88
|
+
never appears either — the async component's `delay` is 300ms and the gap is shorter.
|
|
89
|
+
|
|
90
|
+
⚠ NO COMPOSITING FIX CAN REACH THIS, which is worth stating because styles/base.css already
|
|
91
|
+
carries one for a DIFFERENT white leak on the same seam. `useViewTransition` starts the
|
|
92
|
+
transition 50ms after `beforeResolve` and ends it on `router.afterEach`, and neither waits for
|
|
93
|
+
the slide's async component. The API is asked to cross-fade into a frame that is genuinely
|
|
94
|
+
white, and does so correctly.
|
|
95
|
+
|
|
96
|
+
WHY `load()` AND NOT `preloadRoute()`. The DOM cost that made B unaffordable comes from
|
|
97
|
+
`meta.__preloaded`, which is what puts a route into SlidesShow's `loadedRoutes` and mounts it.
|
|
98
|
+
`route.load()` only runs the slide's dynamic import, filling the `componentsCache` entry that
|
|
99
|
+
`#slidev/slides` keys its `defineAsyncComponent` loader off. So this buys back A's warm chunk
|
|
100
|
+
without buying back any of B's mounted DOM: still one page in the document, and a re-measured
|
|
101
|
+
navigation onto an already-loaded slide reports a single mutation with the layout ALREADY
|
|
102
|
+
present — zero frames of empty slide, zero requests.
|
|
103
|
+
|
|
104
|
+
±1 only, matching what A itself did. A jump from the goto dialog still lands cold.
|
|
105
|
+
*/
|
|
106
|
+
watch([prevRoute, nextRoute], routes => routes.forEach(route => route?.load()), { immediate: true });
|
|
107
|
+
|
|
64
108
|
/*
|
|
65
109
|
task-14b-brief.md — the overlay now also has to show split.vue's Y-ANCHOR and its CONDITIONAL
|
|
66
110
|
gutter, and unlike the three edge insets, both depend on the CURRENT SLIDE's own frontmatter
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanforest/slidev-theme",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Forest brand theme for Slidev — the guardrail. Layouts + components wired to @humanforest/tokens, so any deck is on-brand by construction. Do not hand-pick colours/fonts here; everything aliases the engine tokens.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"slidev-theme",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@humanforest/fonts": "^0.1.
|
|
35
|
-
"@humanforest/frames": "^0.1.
|
|
36
|
-
"@humanforest/tokens": "^0.1.
|
|
37
|
-
"@humanforest/ui": "^0.1.
|
|
34
|
+
"@humanforest/fonts": "^0.1.2",
|
|
35
|
+
"@humanforest/frames": "^0.1.2",
|
|
36
|
+
"@humanforest/tokens": "^0.1.2",
|
|
37
|
+
"@humanforest/ui": "^0.1.2",
|
|
38
38
|
"@iconify-json/lucide": "^1.2.0",
|
|
39
39
|
"@iconify-json/vscode-icons": "^1.2.75",
|
|
40
40
|
"@unovis/ts": "^1.6.7",
|
|
@@ -48,13 +48,18 @@
|
|
|
48
48
|
"vitest": "^4.1.10"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
|
-
"
|
|
51
|
+
"@slidev/client": ">=52",
|
|
52
|
+
"@slidev/types": ">=52",
|
|
53
|
+
"shiki": ">=1",
|
|
54
|
+
"unocss": ">=66",
|
|
55
|
+
"vue": "^3.5"
|
|
52
56
|
},
|
|
53
57
|
"files": [
|
|
54
58
|
"layouts",
|
|
55
59
|
"components",
|
|
56
60
|
"styles",
|
|
57
61
|
"setup",
|
|
62
|
+
"debug",
|
|
58
63
|
"global-top.vue",
|
|
59
64
|
"slide-top.vue",
|
|
60
65
|
"slide-bottom.vue",
|