diff --git a/docs/superpowers/plans/2026-08-13-regex-tester.md b/docs/superpowers/plans/2026-08-13-regex-tester.md new file mode 100644 index 0000000..2677042 --- /dev/null +++ b/docs/superpowers/plans/2026-08-13-regex-tester.md @@ -0,0 +1,61 @@ +# Regex Tester Implementation Plan + +> **For agentic workers:** implement task-by-task. Steps use checkbox (`- [ ]`) syntax. + +**Goal:** Ship a client-side regex tester with live highlighting, capture groups, flags, and per-language code snippets + flavor warnings. + +**Architecture:** Pure lib `regex.lib.ts` (run + highlight + snippet + warnings); thin island `RegexTester.tsx`; registry + SEO. Native `RegExp` only — no new deps. + +**Tech Stack:** Astro + React island, Vitest, native JS `RegExp`. + +## Global Constraints + +- 100% client-side; matching on native `RegExp`. +- New tool `status: 'beta'`; Dev category. +- Bahasa copy uses "tool" loanword, never "alat". +- Commit under the personal noreply identity; no AI-attribution trailers; no absolute machine paths in committed files. + +--- + +### Task 1: Pure lib `regex.lib.ts` (TDD) + +**Files:** Create `src/tools/dev/regex.lib.ts`, Test `src/tools/dev/regex.lib.test.ts`. + +**Interfaces produced:** +- `runRegex(pattern, flags, subject): { matches: RegexMatch[]; error?: string; truncated?: boolean }` +- `RegexMatch = { index: number; value: string; groups: (string|undefined)[]; named: Record }` +- `escapeHtml(s): string` +- `highlightHtml(subject, matches): string` +- `codeSnippet(lang, pattern, flags): string` +- `flavorWarnings(pattern, flags, lang): string[]` +- `FLAGS`, `LANGUAGES`, type `RegexLang` + +- [ ] Write failing tests covering: match/no-match, numbered + named groups, flags g/i/m/s, global iteration + zero-width guard, invalid pattern → error; `highlightHtml` escaping + ``; `codeSnippet` for each of the 7 langs (pattern encoding + flag map) via `it.each`; `flavorWarnings` (Go lookbehind → warns, Go backref → warns, Python named group → warns, Ruby `s`-flag swap → warns, plain pattern → no warnings). +- [ ] Run — confirm fail (module not found). +- [ ] Implement lib. +- [ ] Run — confirm pass. + +### Task 2: Island `RegexTester.tsx` + +**Files:** Create `src/islands/dev/RegexTester.tsx`. + +- [ ] Pattern `TextArea`, flag toggle-chips (segmented), subject `TextArea`. `useMemo` results on `[pattern, flags, subject]`. Highlighted `
` via `dangerouslySetInnerHTML(highlightHtml)`. Match list (count, index, value, groups, named). Language `` (7 langs) → `codeSnippet` shown in a read-only block with `CopyButton`, plus any `flavorWarnings` as `Alert`s / muted notes.
+- i18n `TR: Record`; signature `export default function RegexTester({ lang = 'en' }: { lang?: Lang })`.
+- SSR-safe (RegExp only touched inside `useMemo`/handlers, which is fine, but no module-scope DOM access).
+
+### Registry — `src/registry/tools.ts`
+
+```ts
+{
+  id: 'regex-tester',
+  name: 'Regex Tester',
+  category: 'Dev',
+  route: '/tools/regex-tester',
+  keywords: ['regex', 'regexp', 'regular expression', 'test', 'match', 'pattern', 'pcre', 'javascript', 'python', 'java', 'go'],
+  icon: Regex,
+  summary: 'Test regular expressions with live highlighting and per-language code',
+  load: () => import('@/islands/dev/RegexTester'),
+  status: 'beta'
+},
+```
+`Regex` imported from `lucide-react` (the icon exists; add to the import list).
+
+### SEO — `src/registry/tool-seo.ts`
+
+Add `regex-tester` to both the EN block (~line 9+) and the ID block (~line 1534+). Keyword targets: "regex tester", "test regular expression online", "regex match highlighter", "Python/Java/Go regex". Bahasa uses "tool" loanword, never "alat".
+
+### No new dependency, no globIgnores change
+
+Native `RegExp`; nothing heavy to code-split or exclude from the PWA precache.
+
+## Testing
+
+`src/tools/dev/regex.lib.test.ts` — `runRegex` (match, no-match, capture groups, named groups, each flag, global iteration, zero-width guard, invalid pattern → error), `highlightHtml` (escaping + mark wrapping), `codeSnippet` (per-language literal encoding + flag mapping, `it.each`), `flavorWarnings` (Go lookbehind/backref, Python/Go named groups, Ruby flag swap).
+
+## Definition of done
+
+Spec+plan committed · `regex.lib.ts` unit-tested · vitest + lint + build green · `/tools/regex-tester` builds (EN + ID) · merged to develop · promoted to main · Cloudflare prod build green · live URL verified · user told about PWA hard-refresh.
diff --git a/src/islands/dev/RegexTester.tsx b/src/islands/dev/RegexTester.tsx
new file mode 100644
index 0000000..533380b
--- /dev/null
+++ b/src/islands/dev/RegexTester.tsx
@@ -0,0 +1,206 @@
+import { useMemo, useState } from 'react';
+import { TextArea } from '@/components/ui/TextArea';
+import { Alert } from '@/components/ui/Alert';
+import { CopyButton } from '@/components/ui/CopyButton';
+import {
+  runRegex,
+  highlightHtml,
+  codeSnippet,
+  flavorWarnings,
+  FLAGS,
+  LANGUAGES,
+  type RegexLang,
+} from '@/tools/dev/regex.lib';
+import type { Lang } from '@/i18n/config';
+
+const EXAMPLE_PATTERN = '(?[\\w.]+)@(?[\\w.]+)';
+const EXAMPLE_SUBJECT =
+  'Contact: alice@example.com, bob@work.co.id\nSupport: team@goodwebtools.com\nInvalid: not-an-email @ nope';
+
+const TR: Record string;
+  noMatches: string;
+  truncated: string;
+  groups: string;
+  named: string;
+  language: string;
+  equivalent: string;
+  notes: string;
+}> = {
+  en: {
+    intro: 'Test a regular expression against sample text with live match highlighting, then copy the equivalent code for your language. Everything runs in your browser.',
+    pattern: 'Regular expression',
+    flags: 'Flags',
+    testString: 'Test string',
+    matchesN: (n) => `${n} ${n === 1 ? 'match' : 'matches'}`,
+    noMatches: 'No matches.',
+    truncated: 'Showing the first 10,000 matches.',
+    groups: 'Groups',
+    named: 'Named',
+    language: 'Language',
+    equivalent: 'Equivalent code',
+    notes: 'Flavor notes',
+  },
+  id: {
+    intro: 'Uji ekspresi reguler terhadap teks contoh dengan sorotan kecocokan langsung, lalu salin kode setara untuk bahasa Anda. Semuanya berjalan di browser Anda.',
+    pattern: 'Ekspresi reguler',
+    flags: 'Flag',
+    testString: 'Teks uji',
+    matchesN: (n) => `${n} kecocokan`,
+    noMatches: 'Tidak ada kecocokan.',
+    truncated: 'Menampilkan 10.000 kecocokan pertama.',
+    groups: 'Grup',
+    named: 'Bernama',
+    language: 'Bahasa',
+    equivalent: 'Kode setara',
+    notes: 'Catatan flavor',
+  },
+};
+
+export default function RegexTester({ lang = 'en' }: { lang?: Lang }) {
+  const t = TR[lang] ?? TR.en;
+  const [pattern, setPattern] = useState(EXAMPLE_PATTERN);
+  const [flags, setFlags] = useState('g');
+  const [subject, setSubject] = useState(EXAMPLE_SUBJECT);
+  const [target, setTarget] = useState('python');
+
+  const result = useMemo(() => runRegex(pattern, flags, subject), [pattern, flags, subject]);
+  const html = useMemo(() => highlightHtml(subject, result.matches), [subject, result]);
+  const snippet = useMemo(() => codeSnippet(target, pattern, flags), [target, pattern, flags]);
+  const warnings = useMemo(() => flavorWarnings(pattern, flags, target), [pattern, flags, target]);
+
+  const toggleFlag = (f: string) => {
+    setFlags(prev =>
+      prev.includes(f)
+        ? prev.replace(f, '')
+        : FLAGS.map(x => x.flag).filter(x => prev.includes(x) || x === f).join(''),
+    );
+  };
+
+  const chipClass = (active: boolean) =>
+    `border-2 px-3 py-1 font-mono text-sm font-medium transition-all ${
+      active
+        ? 'border-border bg-accent text-accent-foreground shadow-brutal'
+        : 'border-border hover:shadow-brutal'
+    }`;
+
+  return (
+    
+

{t.intro}

+ +
+ {t.pattern} +