Skip to content

Commit 04f1091

Browse files
committed
Quartz sync: Oct 28, 2025, 6:22 PM
1 parent c3c2346 commit 04f1091

File tree

6 files changed

+249
-12
lines changed

6 files changed

+249
-12
lines changed

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
"mcp__playwright__browser_evaluate",
4141
"mcp__playwright__browser_close",
4242
"mcp__playwright__browser_network_requests",
43-
"Bash(node:*)"
43+
"Bash(node:*)",
44+
"Bash(git log:*)"
4445
],
4546
"deny": [],
4647
"ask": []

CLAUDE.md

Lines changed: 155 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,16 @@ Files (*.md) → Parse (transform with plugins) → Filter (select content) →
3838
```
3939

4040
**Plugin Types:**
41-
1. **Transformers** (14 plugins): Modify markdown/HTML AST during parsing
41+
1. **Transformers** (14 plugins*): Modify markdown/HTML AST during parsing
4242
- Examples: `FrontMatter`, `SyntaxHighlighting`, `ObsidianFlavoredMarkdown`, `TableOfContents`
4343
- Provide text transforms, markdown plugins (remark), and HTML plugins (rehype)
44+
- *Includes `CustomSlug` (user-added); standard Quartz has 13 transformers
4445

45-
2. **Filters** (3 plugins): Decide which content to publish
46-
- Examples: `RemoveDrafts`, `ExplicitFilter`
46+
2. **Filters** (2 plugins): Decide which content to publish
47+
- Examples: `RemoveDrafts`, `ExplicitPublish`
4748
- Return `shouldPublish()` boolean
4849

49-
3. **Emitters** (14 plugins): Generate output files
50+
3. **Emitters** (12 plugins): Generate output files
5051
- Examples: `ContentPage` (HTML pages), `TagPage`, `ContentIndex` (RSS, sitemaps)
5152
- Support incremental builds via `partialEmit()`
5253

@@ -98,7 +99,93 @@ type QuartzComponent = ComponentType<QuartzComponentProps> & {
9899
- `left`/`right`: Sidebars
99100
- Each component contributes CSS/JS resources
100101
101-
**Key Components**: `Explorer` (file tree), `Search`, `TableOfContents`, `Graph`, `Backlinks`, `Darkmode`, etc.
102+
**Key Components**: `Explorer` (file tree), `Search`, `TableOfContents`, `Graph`, `Backlinks`, `Darkmode`, `RecentNotes`, etc.
103+
104+
**Component Inline Scripts Pattern**:
105+
Some components attach client-side behavior via separate `.inline.ts` files imported into the component:
106+
107+
```typescript
108+
// In component TSX file:
109+
// @ts-ignore
110+
import script from "./scripts/recentNotes.inline"
111+
112+
// Component exported with script attached:
113+
RecentNotes.afterDOMLoaded = script
114+
```
115+
116+
Examples: `explorer.inline.ts`, `recentNotes.inline.ts`, `graph.inline.ts`, `graph-loader.inline.ts`
117+
118+
This pattern keeps component logic modular and allows for complex client-side interactions like collapsing, DOM manipulation, and event handling.
119+
120+
### Mobile-First Responsive Architecture
121+
122+
Quartz implements sophisticated mobile-first patterns for optimal UX across devices. The mobile breakpoint is **800px** (defined in `quartz/styles/variables.scss`).
123+
124+
#### Unified Mobile Menu Pattern
125+
126+
On mobile (<800px), Explorer, Search, and RecentNotes merge into a **single overlay menu**:
127+
128+
1. **Opening Explorer on Mobile**:
129+
- Explorer overlay opens with file tree
130+
- Search component is **dynamically moved** into Explorer overlay (at top)
131+
- RecentNotes component is **dynamically moved** into Explorer overlay (at bottom)
132+
- Background scroll locked via `mobile-no-scroll` class on `<html>`
133+
134+
2. **Closing Explorer on Mobile**:
135+
- All components **restored to original DOM positions**
136+
- Scroll lock removed
137+
- RecentNotes collapsed
138+
139+
**Implementation** (`quartz/components/scripts/explorer.inline.ts:29-100`):
140+
- Stores original parent and next sibling references before moving components
141+
- Uses `checkVisibility()` on mobile button to detect viewport size
142+
- `insertBefore()` and `appendChild()` for DOM manipulation
143+
- Components restore to exact original positions on close
144+
145+
#### Desktop Mutual Exclusion
146+
147+
On desktop (≥800px), only **one sidebar can be expanded** at a time:
148+
- Expanding Explorer collapses RecentNotes
149+
- Expanding RecentNotes collapses Explorer
150+
- Prevents overwhelming sidebar content
151+
152+
**Implementation** (`quartz/components/scripts/explorer.inline.ts:103-109` and `recentNotes.inline.ts:24-32`)
153+
154+
#### Viewport Detection Patterns
155+
156+
Two methods used throughout:
157+
158+
1. **`checkVisibility()`** on mobile-only elements:
159+
```typescript
160+
const mobileButton = element.querySelector(".mobile-explorer") as HTMLElement
161+
const isMobile = mobileButton?.checkVisibility()
162+
```
163+
164+
2. **`window.matchMedia()`** for media queries:
165+
```typescript
166+
const isDesktop = window.matchMedia("(min-width: 801px)").matches
167+
```
168+
169+
#### Collapsible Components
170+
171+
**RecentNotes** and **Explorer** both support toggle behavior:
172+
- Fold/unfold SVG icon with rotation animation
173+
- `collapsed` CSS class controls visibility
174+
- `aria-expanded` attribute for accessibility
175+
- Separate mobile/desktop toggle buttons
176+
177+
**Component Scripts**:
178+
- `quartz/components/scripts/explorer.inline.ts`
179+
- `quartz/components/scripts/recentNotes.inline.ts`
180+
181+
#### Scroll Management
182+
183+
When mobile menus open:
184+
```typescript
185+
document.documentElement.classList.add("mobile-no-scroll")
186+
```
187+
188+
Prevents background page scrolling while overlay is active. Removed on close.
102189

103190
### Performance Features
104191

@@ -107,6 +194,15 @@ type QuartzComponent = ComponentType<QuartzComponentProps> & {
107194
3. **Lazy resources**: Client scripts split into pre/post-DOM-ready
108195
4. **Hot reload**: WebSocket notifies browser of rebuilds
109196
5. **Asset optimization**: CSS minified with Lightning CSS, JS bundled with esbuild
197+
6. **Lazy Graph Loading** ⭐: Graph component (D3, Pixi.js, Tween.js) only loads on desktop viewports (>800px), **saving ~1.2MB for mobile users**
198+
- `graph-loader.inline.ts` checks viewport with `window.matchMedia`
199+
- Dynamically loads separate `graph.bundle.js` on-demand
200+
- Queues navigation events while bundle loads
201+
- See `quartz/components/scripts/graph-loader.inline.ts` and `quartz/plugins/emitters/componentResources.ts:buildGraphBundle()`
202+
7. **Critical Script Loading Order** ⚠️: SPA router MUST be loaded first via `unshift()` in `componentResources.ts:addGlobalPageResources()`
203+
- SPA router defines `window.addCleanup` that other component scripts depend on
204+
- Without this order, component cleanup breaks during navigation
205+
- See `quartz/plugins/emitters/componentResources.ts:84-93`
110206

111207
## Key Source Directories
112208

@@ -214,7 +310,8 @@ resources: {
214310
**Config Location**: `quartz.config.ts`
215311

216312
**Common Customizations**:
217-
- **Page title**: `configuration.pageTitle`
313+
- **Page title**: `configuration.pageTitle` - Main site title shown in header
314+
- **Page subtitle**: `configuration.pageSubtitle` - Optional subtitle displayed below title (e.g., "by Author Name")
218315
- **Theme**: `configuration.theme` (light/dark colors, fonts)
219316
- **Plugins**: `plugins.transformers`, `plugins.filters`, `plugins.emitters`
220317
- **Analytics**: `configuration.analytics` (supports Plausible, Google, Umami, etc.)
@@ -265,6 +362,25 @@ MyComponent.beforeDOMLoaded = `// runs before page content loads`
265362
MyComponent.afterDOMLoaded = `// runs after page content loads`
266363
```
267364

365+
### 5. Critical Script Loading Order (⚠️ IMPORTANT)
366+
When adding scripts to `componentResources.afterDOMLoaded` in emitters, use `unshift()` for foundational scripts that other code depends on:
367+
368+
```typescript
369+
// In componentResources.ts:addGlobalPageResources()
370+
if (cfg.enableSPA) {
371+
// SPA router MUST be first - defines window.addCleanup
372+
componentResources.afterDOMLoaded.unshift(spaRouterScript)
373+
}
374+
```
375+
376+
**Why this matters**:
377+
- SPA router defines `window.addCleanup()` used by component scripts for cleanup during navigation
378+
- Component inline scripts (explorer, recentNotes, etc.) call `window.addCleanup()` expecting it to exist
379+
- Loading SPA router last causes "window.addCleanup is not a function" errors
380+
- Always use `unshift()` for foundational utilities, `push()` for dependent scripts
381+
382+
See `quartz/plugins/emitters/componentResources.ts:84-93`
383+
268384
## Custom Slug Plugin
269385

270386
**Location**: `quartz/plugins/transformers/customSlug.ts`
@@ -304,12 +420,40 @@ When running `npx quartz build --serve`:
304420
- Emitter regeneration via `partialEmit()`
305421
- Browser page refresh via WebSocket
306422

307-
## Testing Considerations
423+
## Testing
308424

425+
### Unit Testing
309426
- Test files: `*.test.ts` or `*.test.tsx`
310427
- Run with: `npm run test` (uses tsx test runner)
311428
- No official test suite in main codebase, but plugins can include tests
312429

430+
### Browser Automation Testing (Playwright MCP)
431+
432+
This project uses **Playwright MCP** for visual regression and interaction testing:
433+
434+
- **Test artifacts**: `.playwright-mcp/` directory contains screenshots from test runs
435+
- **Testing focus**: Mobile menu behavior, responsive layouts, dark mode, component interactions
436+
- **Examples of tested scenarios**:
437+
- Mobile menu open/close states
438+
- Search modal behavior
439+
- Explorer/RecentNotes collapsing
440+
- Footer rendering
441+
- Dark mode toggle
442+
443+
**Playwright MCP Access**: Available via Claude Code's MCP integration
444+
- Snapshots: `mcp__playwright__browser_snapshot` - Accessibility tree snapshot
445+
- Screenshots: `mcp__playwright__browser_take_screenshot` - Visual captures
446+
- Interactions: `mcp__playwright__browser_click`, `browser_navigate`, etc.
447+
448+
**Testing Pattern**:
449+
1. Navigate to localhost:8080 (requires `npx quartz build --serve` running)
450+
2. Take snapshots/screenshots to verify UI state
451+
3. Interact with elements (click, type, etc.)
452+
4. Verify responsive behavior at different viewport sizes
453+
5. Store artifacts in `.playwright-mcp/` for comparison
454+
455+
See `.playwright-mcp/` directory for example test artifacts.
456+
313457
## Debugging Tips
314458

315459
1. **Verbose logging**: Check build output for plugin logging
@@ -326,6 +470,10 @@ When running `npx quartz build --serve`:
326470
4. **Relative paths**: Link resolution uses `RelativeURL`; don't mix with other path types
327471
5. **Incremental builds**: Emitters must implement `partialEmit()` to work efficiently in watch mode
328472
6. **Resource conflicts**: Ensure CSS/JS resource names don't conflict across plugins
473+
7. **Script loading order** ⚠️: SPA router and foundational utilities must load first via `unshift()`, not `push()`. Component scripts depend on `window.addCleanup()` from SPA router.
474+
8. **GFM Autolink Literal Bug**: Standard `remark-gfm` treats `@2x` in filenames (e.g., `image@2x.png`) as email addresses. Use `remark-gfm-configurable` with `autolinkLiteral: false` to fix. See `quartz/plugins/transformers/gfm.ts`
475+
9. **Mobile viewport detection**: Use `checkVisibility()` on mobile-only elements OR `window.matchMedia()` for viewport checks. Don't rely solely on CSS classes.
476+
10. **DOM manipulation for mobile**: When moving components between parents, ALWAYS store original parent and next sibling references to restore exact positions. See `explorer.inline.ts` for pattern.
329477

330478
## File Trie & Searching
331479

content/2025/10/20/La Alucinación es el Feature Fundamental de los LLMs.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,9 @@ Esto **no es útil** porque automáticamente le atribuimos una "falta" a un LLM,
4343

4444
Este es el primer artículo de una serie enfocada en los principios y prácticas fundamentales alrededor del desarrollo de software asistido por Inteligencia Artificial Generativa.
4545

46-
[Publicado en LinkedIn en 27 de Octubre del 2025](https://www.linkedin.com/posts/anibalrojas_un-llmnopuedenoalucinar-porque-en-esencia-activity-7388654847971205120-6cmz?utm_source=share&utm_medium=member_desktop&rcm=ACoAAABYcI8BB_U41_Zfnth-a-K6afvWfwlghiM).
46+
En la segunda parte de la serie, [Las matemáticas del Código Asistido por IA](/2025/10/28/las-matematicas-del-codigo-asistido-por-ia), exploro de una forma más formal el impacto de las alucinaciones positivas y negativas en el valor del uso de los Asistentes de Programación.
47+
48+
Compartido y publicado en:
49+
- [LinkedIn el 27 de Octubre del 2025](https://www.linkedin.com/posts/anibalrojas_un-llmnopuedenoalucinar-porque-en-esencia-activity-7388654847971205120-6cmz?utm_source=share&utm_medium=member_desktop&rcm=ACoAAABYcI8BB_U41_Zfnth-a-K6afvWfwlghiM).
50+
- [Substack el 27 de Octubre del 2025](https://open.substack.com/pub/anibal/p/la-alucinacion-es-el-feature-fundamental?r=7wicq&utm_campaign=post&utm_medium=web&showWelcomeOnShare=false).
51+
- [(Twitter) el 27 de Octubre del 2025](https://x.com/anibal/status/1982917439898955921).

content/2025/10/24/Backpressure en contra de las Alucinaciones de los LLMs.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ tags:
44
- llms
55
- backpressure
66
- alucinaciones-negativas
7+
- asistentes-de-programación
8+
- desarrollo-de-software
79
journal: Website
810
journal-date: 2025-10-24
911
date: 2025-10-24
1012
---
1113
> Este es el segundo artículo en la serie Principios Fundamentales para el Desarrollo de Software Asistido por IA, el primero está disponible en [[La Alucinación es el Feature Fundamental de los LLMs]]
1214
13-
No recuerdo cuando fue la primera vez que leí el termino "**backpressure**" usado para englobar los mecanismos que permiten contrarrestar las [[La Alucinación es el Feature Fundamental de los LLMs#^1bb028|Alucinaciones Negativas]] que generan los Coding Assistants, es decir las alucinaciones que *no agregan valor* en el contexto en el que estamos operando.
15+
No recuerdo cuando fue la primera vez que leí el termino "**backpressure**" usado para englobar los mecanismos que permiten contrarrestar las [[La Alucinación es el Feature Fundamental de los LLMs#^1bb028|Alucinaciones Negativas]] que generan los Asistentes de Programación, es decir las alucinaciones que *no agregan valor* en el contexto, code base, en el que estamos trabajando.
1416

15-
**Backpressure** es un termino que originalmente viene de la dinámica de fluidos, y que se refiere a resistencia de un fluido, un gas o un líquido, a fluir cuando se encuentra con una presión "en contra" mayor en su recorrido. Es un término que se ha utilizado también en sistemas de software para describir mecanismos que permiten regular el flujo de datos para evitar la sobrecarga de un sistema "aguas abajo".
17+
**Backpressure** es un termino que originalmente viene de la dinámica de fluidos, y que se refiere a resistencia de un fluido, un gas o un líquido, a fluir cuando se encuentra con una presión "en contra" en su recorrido. Es un término que se ha adoptado también en sistemas de software para describir mecanismos que permiten regular el flujo de datos para evitar la sobrecarga de un sistema "aguas abajo".
1618

17-
Yo una vez me enfrenté a un sistema aguas abajo que era un programa *muy* antiguo que corría en un mainframe y que estaba escrito en Fortran, y solo podía realizar muy pocas operaciones por segundo. Funcionaba, aunque nadie entendía el código, era un cálculo muy complejo, y había que vivir con él y tuvimos que hacer un ajuste para que nuestro sistema no lo destruyera a punta de peticiones.
19+
Yo una vez me enfrenté a un sistema "aguas abajo" que era un programa *antiguo* que corría en un mainframe y que estaba escrito en Fortran, y solo podía realizar muy pocas operaciones por segundo. Funcionaba, aunque nadie entendía el código, era un cálculo muy complejo, y había que vivir con él y tuvimos que hacer un ajuste para que nuestro sistema no lo destruyera a punta de peticiones.
1820

1921
Como es axiomático que los LLMs van a producir [[La Alucinación es el Feature Fundamental de los LLMs#^1bb028|Alucinaciones Negativas]] entonces tenemos que implementar mecanismos que nos permitan minimizarlas y/o contrarrestarlas aka **backpressure**. El mecanismo más obvio en este sentido es el ***human-in-the-loop***, cuando un programador *humano*, con experiencia, revisa el código generado por un AI Assistant y lo acepta o no. Esto tradicionalmente asociado a un Pull Request, Code Review o QA.
2022

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
slug: las-matematicas-del-codigo-asistido-por-ia
3+
draft: false
4+
tags:
5+
- llm
6+
- asistentes-de-programación
7+
- matemáticas
8+
- alucinaciones-negativas
9+
- alucinaciones-positivas
10+
- contexto
11+
- prompting
12+
- valor
13+
date: 2025-10-28
14+
journal: Website
15+
journal-date: 2025-10-28
16+
---
17+
De nuestro primer artículo en la serie, [La Alucinación es el Feature Fundamental de los LLMs](/2025/10/20/la-alucinacion-es-el-feature-fundamental-de-los-llms), tenemos que las alucinaciones de los LLMs pueden ser positivas o negativas según agreguen o sustraigan *valor* a nuestro trabajo, a *"nuestro código"*.
18+
19+
Ahora vamos a dar un paso atrás para aproximarnos un poco más formalmente a qué significa usar un LLM, en particular un **Asistente de Programación** como Claude Code, Cursor, Google CLI, Windsurf, etc en nuestro trabajo con:
20+
21+
```
22+
f(modelo,contexto,prompt) = contexto'
23+
```
24+
25+
Donde:
26+
27+
- **f**: Representa una interacción con el Asistente de Programación
28+
- **modelo** (m): De forma *simplificada*, es un modelo como Claude Sonnet 4.5, Gemini Pro 2.5, ChatGPT 5, etc, pero esto suele ser un *sistema* más complejo como Warp, Github Copilot o Claude Code.
29+
- **contexto** (c): Incluye el código existente, la documentación, *posiblemente las herramientas disponibles*, **no** la *ventana de contexto* que es irrelevante en esta discusión.
30+
- **prompt** (p): Es la instrucción que le damos al Asistente, la cual usualmente implica modificar el código de alguna forma, pero puede ser realizar un commit, instalar un paquete, etc. Excluye hacer una pregunta que solo sea respondida en la UI del Asistente.
31+
- **contexto'** (c'): El nuevo contexto, modificado a raíz del uso del Asistente , el código modificado, un commit, documentación actualizada, un deployment, etc.
32+
33+
Ya con esta base, y de nuevo de forma simplificada, podemos decir que una secuencia de interacciones se representa de esta forma:
34+
35+
```
36+
f(m,c,p₁) = c'
37+
f(m,c',p₂) = c''
38+
f(m,c'',p₃) = c'''
39+
...
40+
```
41+
42+
Donde el contexto va mutando a medida que alimentamos al Asistente con diferentes prompts para el modelo y este "echa código" y/o ejecuta otras acciones. Esta secuencia podría representar lo que conocemos como una sesión.
43+
44+
Por otra parte podemos decir que *contexto'* es igual a *contexto* inicial más las alucinaciones que el Asistente aplicó sobre el mismo, positivas (*a₊*) o negativas (*a₋*). Si estas alucinaciones agregan o quitan LOCs es irrelevante en este momento. Entonces:
45+
46+
```
47+
c' = c + ∑ a₊ + ∑ a₋
48+
```
49+
50+
Por lo que, con una simple sustitución:
51+
52+
```
53+
f(m,c,p) = c + ∑ a₊ + ∑ a₋
54+
```
55+
56+
Y ahora podemos empezar a conectar con el tema del *valor* (*v*). Como las alucinaciones positivas nos agregan valor mientras que las negativas nos lo restan vamos a hacer explícito esto con el símbolo "*-*":
57+
58+
```
59+
v(f(m,c,p)) = v(c) + v(∑ a₊) - v(∑ a₋)
60+
```
61+
62+
Obviamente si las alucinaciones negativas son más que las positivas pues invertimos un montón de nuestro tiempo, generación de tokens, uso de GPUs y electricidad para poco o nada.
63+
64+
**PERO**, la "fórmula" anterior está incompleta, porque determinar si una alucinación es positiva o negativa tiene un costo no trivial. Más aún, descartar, rechazar, corregir las alucinaciones negativas se suma al costo de detectarlas, por esto:
65+
66+
```
67+
v(f(m,c,p)) = v(c) + v(∑ a₊) - v(∑ a₋) - c(∑ a₋)
68+
```
69+
70+
Donde *c(∑ a₋)* es el costo de detectar y remover las alucinaciones negativas porque nosotros solo queremos quedarnos con las alucinaciones positivas. Porque al final queremos aproximarnos lo máximo posible a:
71+
72+
```
73+
f(m,c,p) = c + ∑ a₊
74+
```
75+
76+
Que debido a que [La Alucinación es el Feature Fundamental de los LLMs](/2025/10/20/la-alucinacion-es-el-feature-fundamental-de-los-llms) es no-trivial y siempre tendrá un costo asociado.
77+
78+
Obviamente no pretendo que esto sea un modelo matemático real, sino un artefacto que nos ayude a entender el *sistema* que estamos operando en esta búsqueda de tener una aproximación sistemática y metódica, que *tienda* a producir código de calidad de forma repetible.
79+
80+
Más sobre este "sistema" y cómo podemos operarlo en el próximo post de la serie.

content/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ tags:
1111
- asistentes-de-programación
1212
- claude-code
1313
- agentes
14+
date: 2025-10-27
1415
---
1516
<style>
1617
.hero-image {

0 commit comments

Comments
 (0)