Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions apps/peek-todomvc-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
"preview": "vite preview",
"test": "vitest run"
},
"dependencies": {
"clsx": "^2.1.1",
Expand All @@ -26,6 +27,7 @@
"@vitejs/plugin-react": "^6.0.1",
"tailwindcss": "^4.3.1",
"typescript": "~6.0.3",
"vite": "^8.0.12"
"vite": "^8.0.12",
"vitest": "^3.2.7"
}
}
15 changes: 15 additions & 0 deletions apps/peek-todomvc-demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import { ThemeToggle } from './components/ThemeToggle'
import { TodoList } from './components/TodoList'
import { useTheme } from './hooks/useTheme'
import { useTodos } from './hooks/useTodos'
import { isBugMode } from './lib/bugMode'
import { sortByPriority } from './lib/sortByPriority'

function App() {
const todos = useTodos()
const { theme, toggle } = useTheme()
const inputRef = useRef<HTMLInputElement>(null)
const bugMode = isBugMode(window.location.search)

// Keyboard shortcuts: "/" focuses the input, 1/2/3 switch filters.
useEffect(() => {
Expand Down Expand Up @@ -75,6 +78,18 @@ function App() {
onClearCompleted={todos.clearCompleted}
/>
)}
{bugMode && todos.todos.length > 0 && (
<div className="flex justify-center border-t border-border px-4 py-2">
<button
type="button"
// Intentional demo bug (see sortByPriority): throws in this handler.
onClick={() => todos.reorder(sortByPriority(todos.todos))}
className="text-sm text-muted-foreground underline decoration-dotted hover:text-foreground"
>
Sort by priority
</button>
</div>
)}
</motion.main>

<footer className="mt-6 space-y-1 text-center text-xs text-muted-foreground/70">
Expand Down
16 changes: 16 additions & 0 deletions apps/peek-todomvc-demo/src/lib/bugMode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest'
import { isBugMode } from './bugMode'

describe('isBugMode', () => {
it('is true only when bug=1 is present', () => {
expect(isBugMode('?bug=1')).toBe(true)
expect(isBugMode('bug=1')).toBe(true)
expect(isBugMode('?bug=1&x=2')).toBe(true)
})

it('is false by default / for other values', () => {
expect(isBugMode('')).toBe(false)
expect(isBugMode('?bug=0')).toBe(false)
expect(isBugMode('?other=1')).toBe(false)
})
})
8 changes: 8 additions & 0 deletions apps/peek-todomvc-demo/src/lib/bugMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* True when the demo is launched in reproducible-bug mode (`?bug=1`).
* Gates the intentionally-buggy "Sort by priority" affordance used by the
* Slack-connector case study, so the default demo stays clean.
*/
export function isBugMode(search: string): boolean {
return new URLSearchParams(search).get('bug') === '1'
}
16 changes: 16 additions & 0 deletions apps/peek-todomvc-demo/src/lib/sortByPriority.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest'
import type { Todo } from '../types'
import { sortByPriority } from './sortByPriority'

function makeTodo(title: string): Todo {
return { id: title, title, completed: false, createdAt: 0 }
}

describe('sortByPriority (intentional demo bug)', () => {
it('throws a TypeError on real todos (no migrated priority field)', () => {
expect(() => sortByPriority([makeTodo('a'), makeTodo('b')])).toThrow(TypeError)
expect(() => sortByPriority([makeTodo('a'), makeTodo('b')])).toThrow(
/Cannot read properties of undefined \(reading 'level'\)/,
)
})
})
20 changes: 20 additions & 0 deletions apps/peek-todomvc-demo/src/lib/sortByPriority.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Todo } from '../types'

/**
* Sort todos by their `priority.level`.
*
* KNOWN BUG — reproducible via `?bug=1`. The "priority" feature shipped its UI
* but never migrated a `priority` value onto existing/persisted todos, so
* `priority` is `undefined` at runtime. A loose cast (the kind added to "move
* fast") hides this from the type-checker, so it compiles clean and throws only
* at runtime, in the click handler:
* TypeError: Cannot read properties of undefined (reading 'level')
* This is intentional — it is the failure the case study debugs from Slack.
*/
export function sortByPriority(todos: Todo[]): Todo[] {
return [...todos].sort((a, b) => {
const pa = (a as Todo & { priority: { level: number } }).priority
const pb = (b as Todo & { priority: { level: number } }).priority
return pa.level - pb.level
})
}
8 changes: 8 additions & 0 deletions apps/peek-todomvc-demo/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
include: ['src/**/*.test.ts'],
environment: 'node',
},
})
91 changes: 91 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading