diff --git a/content/worksheet.md b/content/worksheet.md new file mode 100644 index 0000000..6feb813 --- /dev/null +++ b/content/worksheet.md @@ -0,0 +1,71 @@ +--- +title: Workshop Materiaal +subtitle: Alles wat je nodig hebt tijdens de Claude Code Workshop +--- + +## Download Superpowers + +Superpowers is een plugin voor Claude Code die extra mogelijkheden toevoegt. Denk aan gestructureerd plannen, test-driven development en slimmer debuggen. Tijdens de workshop gebruiken we deze plugin. + +:::tip +Download het zipbestand en installeer het in Claude Code. Ga naar Customize > Personal Plugin (+) > Create Plugin > Upload Plugin en zet daar de zipfile in (niet uitgepakt) +::: + +[Download Superpowers (zip)](superpowers-main.zip) + +## Start Prompt + +Gebruik deze prompt als startpunt om te brainstormen met Claude Code over je project. Plak hem in je terminal en vervang `{HIER JE IDEE}` door jouw projectidee. Aan het einde kun je het plan nog aanpassen of extra's aan toevoegen. + +:::command[Start prompt] +Ik wil {HIER JE IDEE}. + +Stel me maximaal 5 vragen om mijn idee beter te begrijpen. Stel ze een voor een met AskUserQuestions, niet allemaal tegelijk. + +Maak daarna een bouwplan (PRD) met: +- Wat het doet (in gewone taal) +- Hoe het eruitziet (beschrijf de interface) +- Zelfstandige bouwfasen (max 3-4), in volgorde van prioriteit. Fase 1 is het minimum dat waarde oplevert. +Elke fase levert iets werkends op. + +Als ik het bouwplan goedkeur: +1. Sla het op als PRD.md in de projectmap +2. Maak een CLAUDE.md aan met een samenvatting van het project, de bouwfasen en hun status (allemaal "nog niet gestart") +3. Stop. Ga niet verder met bouwen tot ik het zeg. + +Beperkingen: +- Alleen lokaal, geen hosting of externe diensten +- Gebruik HTML/CSS/JavaScript waar mogelijk (geen framework nodig voor eenvoudige projecten) +- Node.js als een server nodig is +- Python alleen als Node.js echt niet volstaat +::: + +## Tips tijdens het bouwen + +### Sessiemanagement +- **Start een nieuwe sessie per bouwfase** - niet alles in een lange sessie proberen +- Elke sessie: open de PRD, zeg "bouw fase X" en go. +- Korte sessies zijn efficienter dan lange (minder context = minder tokens = snellere antwoorden) +- **Kernboodschap:** je PRD is je anker. Bewaar die goed, dan kun je altijd opnieuw starten. + + +### Waarom niet een lange sessie? +- Pro-limiet bereik je sneller in een lange sessie (meer context = meer tokenverbruik) +- Vroege signalen dat je limiet nadert: tragere antwoorden, kortere code, stappen overslaan +- Als je de melding "usage limit reached" krijgt ben je te laat en zul je een paar uur moeten wachten +- Tenzij je een Extra Usage Wallet hebt ingesteld (Settings > Usage) +- **Preventie is beter:** plan je sessies per fase, dan is de kans kleiner dat je tegen limieten loopt + + +### Workflow +1. Brainstorm en PRD schrijven (sessie 1) +2. Nieuwe sessie in dezelfde werkmap: "bouw fase 1" (sessie 2) +3. Nieuwe sessie: "bouw fase 2" (sessie 3) +4. Herhaal tot prototype klaar is + +### Agents (voor gevorderden / Max-gebruikers) +- Claude Code zet zelf al subagents in wanneer het nuttig is - daar hoef je niks voor te doen +- Expliciet aansturen kan: "Zet waar mogelijk agents in om parallelle taken tegelijk te doen" +- **Let op:** agents verbruiken meer tokens, niet minder - elke agent heeft zijn eigen context +- Voor Pro-gebruikers die al tegen limieten aanlopen versnelt dat het probleem +- **Advies:** alleen actief inzetten als je op Max zit of een API key gebruikt \ No newline at end of file diff --git a/public/superpowers-main.zip b/public/superpowers-main.zip new file mode 100644 index 0000000..25348f6 Binary files /dev/null and b/public/superpowers-main.zip differ diff --git a/src/components/editor/MarkdownEditor.jsx b/src/components/editor/MarkdownEditor.jsx index 72b3ef4..67ec9b2 100644 --- a/src/components/editor/MarkdownEditor.jsx +++ b/src/components/editor/MarkdownEditor.jsx @@ -21,7 +21,7 @@ import { useKeyboardShortcuts } from './useKeyboardShortcuts'; * @param {string} props.content - Huidige Markdown content * @param {function} props.onContentChange - Callback bij wijzigingen */ -export default function MarkdownEditor({ content, onContentChange }) { +export default function MarkdownEditor({ content, onContentChange, filename = 'installatie.md' }) { const [isOpen, setIsOpen] = useState(true); const [isDirty, setIsDirty] = useState(false); const [isSaving, setIsSaving] = useState(false); @@ -41,7 +41,7 @@ export default function MarkdownEditor({ content, onContentChange }) { const response = await fetch('/__editor/save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ content }), + body: JSON.stringify({ content, filename }), }); const result = await response.json(); @@ -88,7 +88,7 @@ export default function MarkdownEditor({ content, onContentChange }) {

Markdown Editor

- installatie.md + {filename}
diff --git a/src/lib/markdown/directives.js b/src/lib/markdown/directives.js index c43d386..151b2a4 100644 --- a/src/lib/markdown/directives.js +++ b/src/lib/markdown/directives.js @@ -101,6 +101,14 @@ function parseDirectives(text) { continue; } + // :::tip + if (line.trim() === ':::tip') { + const result = parseEnclosed(lines, i); + blocks.push({ type: 'tip', content: result.content }); + i = result.nextIndex; + continue; + } + // :::steps if (line.trim() === ':::steps') { const result = parseEnclosed(lines, i); diff --git a/src/lib/markdown/render.jsx b/src/lib/markdown/render.jsx index b746254..1414926 100644 --- a/src/lib/markdown/render.jsx +++ b/src/lib/markdown/render.jsx @@ -7,7 +7,7 @@ * Afbeeldingpaden worden automatisch geprefixed met BASE_URL. */ -import { useState } from 'react'; +import { useState, useCallback } from 'react'; import { marked } from 'marked'; const BASE_URL = import.meta.env.BASE_URL; @@ -48,6 +48,8 @@ function RenderBlock({ block, context }) { return ; case 'info': return ; + case 'tip': + return ; case 'steps': return ; case 'checklist': @@ -196,6 +198,21 @@ function InfoBlock({ content }) { ); } +/** + * Tipblok (gele achtergrond met lampje). + */ +function TipBlock({ content }) { + return ( +
+ 💡 +
+
+ ); +} + /** * Genummerde stappen met coral cirkel-iconen. * Gebruikt pre-parsed steps uit de directive parser. @@ -274,15 +291,46 @@ function ChecklistVerifyBlock({ content }) { } /** - * Terminal commando blok (donkere achtergrond). + * Terminal commando blok (donkere achtergrond) met kopieerknop. */ function CommandBlock({ command, content }) { + const [copied, setCopied] = useState(false); + + const handleCopy = useCallback(() => { + navigator.clipboard.writeText(content).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }, [content]); + return ( -
-
- {command} +
+
+
+ {command} + +
+
{content}
-

{content}

); } diff --git a/src/main.jsx b/src/main.jsx index 73542c4..83f639d 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -10,6 +10,7 @@ import Signup from './pages/Signup.jsx' import WaitlistSignup from './pages/WaitlistSignup.jsx' import WaitlistThankYou from './pages/WaitlistThankYou.jsx' import Installatie from './pages/Installatie.jsx' +import Worksheet from './pages/Worksheet.jsx' createRoot(document.getElementById('root')).render( @@ -23,6 +24,7 @@ createRoot(document.getElementById('root')).render( } /> } /> } /> + } /> , diff --git a/src/pages/Worksheet.jsx b/src/pages/Worksheet.jsx new file mode 100644 index 0000000..6b56af8 --- /dev/null +++ b/src/pages/Worksheet.jsx @@ -0,0 +1,105 @@ +/** + * Worksheet.jsx - Materiaal voor workshop deelnemers + * + * Rendert content uit content/worksheet.md via de custom Markdown parser. + * Bevat downloads, prompts en ander workshopmateriaal. + * + * Editor modus: bereikbaar via /worksheet?editor= + * De secret wordt geconfigureerd in src/config/editor.js + */ + +import { useState, lazy, Suspense } from 'react'; +import { Link, useSearchParams } from 'react-router-dom'; +import { parseDocument } from '../lib/markdown/directives'; +import { renderBlocks } from '../lib/markdown/render'; +import { EDITOR_CONFIG } from '../config/editor'; +import worksheetContent from '../../content/worksheet.md?raw'; + +// Editor lazy-loaded zodat het niet in de productie bundle zit +const MarkdownEditor = lazy(() => import('../components/editor/MarkdownEditor')); + +function Worksheet() { + const [searchParams] = useSearchParams(); + const [content, setContent] = useState(worksheetContent); + + // Editor alleen beschikbaar in dev mode (niet op productie) + const showEditor = + import.meta.env.DEV && + EDITOR_CONFIG.enabled && + searchParams.get('editor') === EDITOR_CONFIG.secret; + + // Parse de Markdown content + const { frontmatter, blocks } = parseDocument(content); + + // Context voor de renderer + const context = {}; + + return ( +
+ {/* Header */} +
+
+ + + + + Terug naar workshop + +
+
+ + {/* Content */} +
+
+ {/* Titel en subtitel uit frontmatter */} + {frontmatter.title && ( +

{frontmatter.title}

+ )} + {frontmatter.subtitle && ( +

{frontmatter.subtitle}

+ )} + + {/* Gerenderde Markdown blocks */} + {renderBlocks(blocks, context)} +
+
+ + {/* Contact */} +
+

+ Kom je er niet uit? Mail naar{' '} + + frank@frankmeeuwsen.com + +

+
+ + {/* Editor overlay (alleen zichtbaar met juiste URL parameter) */} + {showEditor && ( + +
Editor laden...
+
+ }> + + + )} + + {/* Footer */} + +
+ ); +} + +export default Worksheet; diff --git a/vite-plugin-editor-api.js b/vite-plugin-editor-api.js index 060cafa..2c8a74d 100644 --- a/vite-plugin-editor-api.js +++ b/vite-plugin-editor-api.js @@ -28,8 +28,10 @@ export function editorApiPlugin() { req.on('data', chunk => { body += chunk; }); req.on('end', () => { try { - const { content } = JSON.parse(body); - const filePath = path.join(rootDir, 'content', 'installatie.md'); + const { content, filename } = JSON.parse(body); + // Bestandsnaam uit request, fallback naar installatie.md + const safeName = (filename || 'installatie.md').replace(/[^a-zA-Z0-9._-]/g, ''); + const filePath = path.join(rootDir, 'content', safeName); // Zorg dat de content directory bestaat const dir = path.dirname(filePath);