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: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,9 @@ dist-ssr
claude.md

package-lock.json

# Playwright (baselines under e2e/**-snapshots are committed; artifacts are not)
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
26 changes: 26 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Agent Guidelines for Mitos

## Commands

- **Build**: `bun run build` (tsc + vite)
- **Lint**: `bun run lint` (eslint app)
- **Format**: `bun run fmt` (write) or `bun run fmt:check` (check)
- **Test**: `bun test` (unit, in `tests/`) or `bun test <file>` (single test)
- **E2E**: `bun run test:e2e` (Playwright render snapshots in `e2e/`; needs
`bunx playwright install chromium` once). Regenerate baselines per-platform with
`bun run test:e2e:update`.
- **Dev**: `bun run dev`
- **CI**: `bun run ci` (runs fmt:check, tsc, lint, test)

## Code Style

- **Formatting**: Prettier with 92 char width, no semicolons, single quotes, trailing commas
- **Imports**: Sort order: third-party → `~/*` (app alias) → relative. Use `~/` for app/
imports.
- **TypeScript**: Strict mode enabled. Prefix unused vars/params with `_`. Use explicit
types.
- **Naming**: camelCase for vars/functions, PascalCase for components/types
- **ESLint rules**: Use `===`, no param reassignment, no return assignment
- **Error handling**: Use toast (sonner) for user-facing errors
- **License**: Add MPL 2.0 header to all new source files
- **Tests**: Use Bun test with describe/test/expect. See tests/ for examples.
36 changes: 26 additions & 10 deletions app/components/ascii-animation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default function AsciiAnimation({
setAnimationController,
textColor,
backgroundColor,
canvasBackgroundColor = backgroundColor,
padding,
children,
}: {
Expand All @@ -29,10 +30,14 @@ export default function AsciiAnimation({
setAnimationController: (controller: AnimationController) => void
textColor: string
backgroundColor: string
// Background used to fill the canvas itself. Defaults to `backgroundColor`,
// but can be set to 'transparent' so the underlying image (rendered behind
// the canvas but in front of the container background) shows through.
canvasBackgroundColor?: string
padding: number
children: ReactNode
}) {
const asciiEl = useRef<HTMLPreElement>(null)
const asciiEl = useRef<HTMLCanvasElement>(null)
const controllerRef = useRef(animationController)

const containerRef = useRef<HTMLDivElement>(null)
Expand All @@ -48,8 +53,8 @@ export default function AsciiAnimation({
const element = asciiEl.current
if (!container || !element) return

const width = Math.floor(element.offsetWidth + padding * 2)
const height = Math.floor(element.offsetHeight + padding * 2)
const width = Math.floor(element.offsetWidth)
const height = Math.floor(element.offsetHeight)

container.style.width = `${width}px`
container.style.height = `${height}px`
Expand Down Expand Up @@ -94,6 +99,9 @@ export default function AsciiAnimation({
element: asciiEl.current,
onFrameUpdate: onFrameUpdate ? onFrameUpdate : undefined,
maxFrames,
textColor,
backgroundColor: canvasBackgroundColor,
padding,
})

animController.togglePlay(wasPlaying)
Expand All @@ -103,26 +111,34 @@ export default function AsciiAnimation({
} catch (error) {
console.error('Error creating animation controller:', error)
}
}, [program, maxFrames, onFrameUpdate, setAnimationController])
}, [
program,
maxFrames,
onFrameUpdate,
setAnimationController,
textColor,
canvasBackgroundColor,
padding,
])

return (
<div
ref={containerRef}
className="ascii-animation relative flex items-center justify-center [font-size:0px]"
className="ascii-animation relative flex items-center justify-center overflow-hidden rounded-[1%] [font-size:0px]"
aria-hidden
role="img"
style={{
backgroundColor,
padding,
}}
>
<pre
<canvas
ref={asciiEl}
className="pointer-events-none relative z-10 m-0 select-none whitespace-pre p-0 font-mono leading-[1.2]"
id="ascii-canvas"
className="pointer-events-none relative z-10 m-0 select-none"
style={{
fontFamily: '"GT America Mono",monospace',
fontFamily: '"GT America Mono", monospace',
fontSize: '12px',
color: textColor,
lineHeight: '1.2',
}}
/>
{children}
Expand Down
3 changes: 2 additions & 1 deletion app/components/ascii-art-generator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -717,8 +717,9 @@ export function AsciiArtGenerator() {
setTemplateType('custom')

toast(`${projectData.name} has been loaded successfully.`)
} catch (_error) {
} catch (error) {
toast('The selected file is not a valid project file')
console.error(error)
}
}

Expand Down
39 changes: 8 additions & 31 deletions app/components/ascii-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export function AsciiPreview({
const [position, setPosition] = useState({ x: 0, y: 0 })
const [isDragging, setIsDragging] = useState(false)
const [dragStart, setDragStart] = useState({ x: 0, y: 0 })
const [autoFit, setAutoFit] = useState(false)
const [autoFit, setAutoFit] = useState(true)
const prevDimensionsRef = useRef(dimensions)

const containerSize = useSize(container)
Expand All @@ -153,7 +153,7 @@ export function AsciiPreview({
const zoomFactor = 0.035 * (e.deltaY > 0 ? 1 : 1.1)

if (e.deltaY < 0) {
setZoomLevel((prev) => Math.min(prev * (1 + zoomFactor), 5))
setZoomLevel((prev) => Math.min(prev * (1 + zoomFactor), 3))
} else {
setZoomLevel((prev) => Math.max(prev / (1 + zoomFactor), 0.5))
}
Expand Down Expand Up @@ -331,7 +331,7 @@ export function AsciiPreview({
</div>
)}
<div
className="duration-50 relative transform-gpu overflow-hidden rounded-[1%] transition-transform ease-out"
className="duration-50 relative transform-gpu rounded-[1%] transition-transform ease-out"
style={{
transform: isExporting
? 'none'
Expand All @@ -349,6 +349,11 @@ export function AsciiPreview({
setAnimationController={setAnimationController}
textColor={settings.textColor}
backgroundColor={settings.backgroundColor}
canvasBackgroundColor={
showUnderlyingImage && underlyingImageUrl && !isExporting
? 'transparent'
: settings.backgroundColor
}
padding={paddingPixels}
>
{/* Show underlying image if enabled */}
Expand Down Expand Up @@ -390,34 +395,6 @@ export function AsciiPreview({
)
}

export const getContent = (dimensions: { width: number; height: number }) => {
const asciiElement = document.querySelector('.ascii-animation pre')

if (asciiElement) {
const rawContent = asciiElement.textContent || ''
const { width, height } = dimensions

// Process the raw content into properly formatted lines
const formattedLines = []

for (let i = 0; i < height; i++) {
// Extract exactly width characters for each line
const lineStart = i * width
const lineEnd = lineStart + width

// Ensure we don't go out of bounds
if (lineStart < rawContent.length) {
const line = rawContent.substring(lineStart, Math.min(lineEnd, rawContent.length))
// Add the line without right trimming to preserve spaces
formattedLines.push(line)
}
}

// Join the lines with newlines
return formattedLines.join('\n')
}
}

function FrameSlider({
frame,
totalFrames,
Expand Down
Loading
Loading