Add multimodal AI event creation: Users can attach images (screenshots, flyers) alongside or instead of text prompts for AI-powered calendar event extraction
Strengthen input/output validation: Zod schemas with regex MIME validation, binary size checking, and ISO datetime format enforcement on both request and response boundaries
Fix all linter and typecheck warnings: Zero errors across bun run typecheck and bun run lint (ESLint + Biome)
Changes
New Features
ImagePicker component for file upload (PNG, JPEG, WebP)
Image preview with remove button in AIToolbar
Drag-and-drop image support in DragDropContainer
Multimodal API route with OpenRouter chat API for base64 images
Zod schemas for AI event request validation and response parsing
Security & Validation
Server-side: regex MIME type validation (data:image/(png|jpeg|webp);base64,...), actual binary size check (≤10MB)
Server-side: ISO datetime validation on AI response start/end fields
Server-side: generic error messages to client (no raw AI response leakage)
Client-side: file type and size validation before base64 encoding
DATABASE_URL env var validation with descriptive error instead of non-null assertion
Code Quality
Extracted extractJsonFromText to shared src/lib/json-utils.ts
Extracted shared image constants to src/lib/constants.ts
Refactored 88-line handleAiCreate into focused functions (sendAiRequest, populateEventForm, persistAiEvents)
Fixed Promise constructor anti-pattern and setAiLoading race condition
Used extractContentFromChatResponse() instead of nested ternary fallthrough
Derived CalendarEvent type from AiEventResponseItem
Derived ImagePicker variant/size types from buttonVariants via VariantProps
Environment-based model config via process.env.AI_MODEL
Tooling
Added biome.json with Tailwind CSS directive support
Added src/css.d.ts module declaration for TypeScript
CSS formatting standardized via Biome
All ESLint warnings resolved (unused vars, catch params)
All Biome warnings resolved (parseInt radix, array index keys, aria-hidden, non-null assertions)
Files Changed
253 files changed (+39,109 / -415 lines)
Most changes are .opencode/ context files and lockfile updates
Core feature: ~10 source files
Test Plan
bun run typecheck passes with zero errors
bun run lint passes with zero errors/warnings
Manual: text-only AI event creation
Manual: image-only AI event creation
Manual: text + image AI event creation
Manual: drag-and-drop image onto calendar
Manual: oversized/invalid file rejection
## Summary
- **Add multimodal AI event creation**: Users can attach images (screenshots, flyers) alongside or instead of text prompts for AI-powered calendar event extraction
- **Strengthen input/output validation**: Zod schemas with regex MIME validation, binary size checking, and ISO datetime format enforcement on both request and response boundaries
- **Fix all linter and typecheck warnings**: Zero errors across `bun run typecheck` and `bun run lint` (ESLint + Biome)
## Changes
### New Features
- `ImagePicker` component for file upload (PNG, JPEG, WebP)
- Image preview with remove button in `AIToolbar`
- Drag-and-drop image support in `DragDropContainer`
- Multimodal API route with OpenRouter chat API for base64 images
- Zod schemas for AI event request validation and response parsing
### Security & Validation
- Server-side: regex MIME type validation (`data:image/(png|jpeg|webp);base64,...`), actual binary size check (≤10MB)
- Server-side: ISO datetime validation on AI response `start`/`end` fields
- Server-side: generic error messages to client (no raw AI response leakage)
- Client-side: file type and size validation before base64 encoding
- `DATABASE_URL` env var validation with descriptive error instead of non-null assertion
### Code Quality
- Extracted `extractJsonFromText` to shared `src/lib/json-utils.ts`
- Extracted shared image constants to `src/lib/constants.ts`
- Refactored 88-line `handleAiCreate` into focused functions (`sendAiRequest`, `populateEventForm`, `persistAiEvents`)
- Fixed Promise constructor anti-pattern and `setAiLoading` race condition
- Used `extractContentFromChatResponse()` instead of nested ternary fallthrough
- Derived `CalendarEvent` type from `AiEventResponseItem`
- Derived `ImagePicker` variant/size types from `buttonVariants` via `VariantProps`
- Environment-based model config via `process.env.AI_MODEL`
### Tooling
- Added `biome.json` with Tailwind CSS directive support
- Added `src/css.d.ts` module declaration for TypeScript
- CSS formatting standardized via Biome
- All ESLint warnings resolved (unused vars, catch params)
- All Biome warnings resolved (parseInt radix, array index keys, aria-hidden, non-null assertions)
## Files Changed
- 253 files changed (+39,109 / -415 lines)
- Most changes are `.opencode/` context files and lockfile updates
- Core feature: ~10 source files
## Test Plan
- [x] `bun run typecheck` passes with zero errors
- [x] `bun run lint` passes with zero errors/warnings
- [x] Manual: text-only AI event creation
- [x] Manual: image-only AI event creation
- [x] Manual: text + image AI event creation
- [x] Manual: drag-and-drop image onto calendar
- [x] Manual: oversized/invalid file rejection
Sort imports alphabetically, convert value imports to type-only where
appropriate, normalize indentation to tabs, and sort exports
alphabetically across UI components, pages, and lib modules.
Move image extensions, MIME types, and size limit into a dedicated
constants module. Extract JSON-from-text parsing into a pure utility
function for reuse across the codebase.
Add regex-based data URL validation for images, compute binary size
from base64 for accurate 10MB limit, enforce datetime strings with
offset for start/end fields, and derive CalendarEvent from the AI
response item type to eliminate field duplication.
Replace inline JSON extraction with the shared json-utils module,
extract chat response content parsing into a dedicated helper, make
the AI model configurable via AI_MODEL env var, and improve error
messages for production safety.
Break down the monolithic handleAiCreate into focused helpers
(sendAiRequest, persistAiEvents, populateEventForm), add client-side
image file validation before upload, and use toast.promise finally
callback for loading state cleanup.
Add unoptimized prop to image preview to support blob URLs, contain
overflow on preview container, replace div with semantic section
element in DragDropContainer with aria-label, and import shared
image constants.
Replace manually duplicated variant/size type literals with
VariantProps<typeof buttonVariants> for type safety and
consistency with the Button component.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
bun run typecheckandbun run lint(ESLint + Biome)Changes
New Features
ImagePickercomponent for file upload (PNG, JPEG, WebP)AIToolbarDragDropContainerSecurity & Validation
data:image/(png|jpeg|webp);base64,...), actual binary size check (≤10MB)start/endfieldsDATABASE_URLenv var validation with descriptive error instead of non-null assertionCode Quality
extractJsonFromTextto sharedsrc/lib/json-utils.tssrc/lib/constants.tshandleAiCreateinto focused functions (sendAiRequest,populateEventForm,persistAiEvents)setAiLoadingrace conditionextractContentFromChatResponse()instead of nested ternary fallthroughCalendarEventtype fromAiEventResponseItemImagePickervariant/size types frombuttonVariantsviaVariantPropsprocess.env.AI_MODELTooling
biome.jsonwith Tailwind CSS directive supportsrc/css.d.tsmodule declaration for TypeScriptFiles Changed
.opencode/context files and lockfile updatesTest Plan
bun run typecheckpasses with zero errorsbun run lintpasses with zero errors/warnings