|
All checks were successful
Deploy to Cloudflare Workers / deploy (push) Successful in 1m37s
## Problem A user on iOS (iPhone, Safari 18.1.1) reported being unable to upload item images. The feature works on Linux and Android. Cloudflare logs showed minimal diagnostic information. ## Root Cause Analysis ### 1. Critical: Race condition unmounts form during upload The `onSubmit` flow called `onSave(result.data, true)` **before** `uploadImages()` completed. This triggered `handleSave` in `ItemFormPage`, which set `success=true`, causing React to unmount `ItemForm` and render the success message instead. The unmount cleanup fired `abortControllerRef.current.abort()`, killing all in-flight image uploads. The `isUploading` parameter was accepted in the type signature but completely ignored by `handleSave`. This race existed on all platforms but iOS Safari is more aggressive about unmount timing, making the window tighter. ### 2. High: HEIC/HEIF not accepted iOS cameras default to HEIC format. The file input `accept` attribute and `ALLOWED_TYPES` only included JPEG/PNG/WebP. While iOS Safari auto-converts HEIC on selection, the conversion is inconsistent across iOS versions — sometimes reporting empty `file.type` or `image/heif`, which `validateImageFile` rejected. ### 3. Medium: Web Workers in image compression `browser-image-compression` with `useWebWorker: true` uses `OffscreenCanvas` and `createImageBitmap` inside workers. iOS Safari has had longstanding issues with these APIs in worker contexts. ### 4. Medium: No FileReader error handler If `FileReader.readAsDataURL` failed (e.g., corrupted compression output), `loadedCount` never reached `files.length`, leaving the UI stuck in "Processing images..." state forever. ## Changes ### `src/components/react/ItemForm.tsx` - **Fix race condition**: Remove premature `onSave(result.data, true)` call. Now `onSave` is called only once, after uploads complete - **HEIC/HEIF support**: Add to file input `accept` attribute - **Disable web workers**: Set `useWebWorker: false` for iOS compatibility - **FileReader error handler**: Prevent stuck processing state - **Preserve compression output type**: Use `compressedBlob.type` instead of hardcoding `image/jpeg` - **Diagnostic logging**: Log file metadata on selection, compression failures with details, and abort events ### `src/lib/storage.ts` - **Magic byte validation**: Replace MIME-type-only validation with binary header inspection. Detects JPEG, PNG, WebP, and HEIC/HEIF from file headers. Files with empty/generic MIME types (common iOS quirk) are accepted only if magic bytes confirm they are real images. This prevents attackers from uploading non-image files (SVG with JS, HTML) by spoofing MIME types. - **HEIC/HEIF in allowed types**: Add to `ALLOWED_TYPES` and MIME-to-extension map - **Upload logging**: Log file path, type, and size at upload start ### `tests/unit/storage.test.ts` - Rewrite mock file helpers to use real magic byte headers - Add tests for HEIC/HEIF acceptance and extension mapping - Add tests for empty MIME type + valid magic bytes (iOS scenario) - Add tests for invalid magic bytes rejection - Add RIFF-without-WEBP rejection test ## Testing - [ ] Upload image on desktop browser (regression check) - [ ] Upload image on iOS Safari (if available) - [ ] Upload HEIC photo from iOS camera roll - [ ] Verify success redirect waits for upload to complete - [ ] Check browser console for new diagnostic logging - [ ] `just test-unit` — all 331 tests pass Reviewed-on: #80 Co-authored-by: exe-dev-bot <exe.dev@kwila.cloud> Co-committed-by: exe-dev-bot <exe.dev@kwila.cloud> |
||
|---|---|---|
| .agents/skills/specture | ||
| .bots | ||
| .forgejo/workflows | ||
| .opencode/command | ||
| .vscode | ||
| docs | ||
| public | ||
| scripts | ||
| specs | ||
| src | ||
| supabase | ||
| tests | ||
| .env.example | ||
| .envrc | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| .prettierignore | ||
| .prettierrc | ||
| AGENTS.md | ||
| astro.config.mjs | ||
| CONTRIBUTING.md | ||
| eslint.config.js | ||
| flake.lock | ||
| flake.nix | ||
| justfile | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.ts | ||
| README.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
| wrangler.jsonc | ||
Market
Find what you need through people you trust
Note
We are still determining a final name for the market. Let us know if you have ideas - market@kwila.cloud.
Problems to Solve
Used Market
If you are looking for a good deal on something on the used market, it takes way too much time and effort to research and find what you need.
What if you could simply post what you're looking for - "need a used stove, $200 budget" or "need a house cleaner" - and your community could help you find what you are looking for?
Small Businesses
If you are looking to have a digital presence for your small business without breaking the bank investing in a dedicated website, what do you do?
What if you could quickly and easily set up a vendor page to list your inventory and portfolio?
Our Solution
An online marketplace designed to foster authentic interactions and high-quality transactions within trusted communities. Built for local artisans, small businesses, and individuals who value relationships over profit margins.
This market exists to serve people and communities, not investors or growth metrics. We're building a platform for small-scale, high-trust commerce that keeps the focus on authentic human relationships.
How It Works
- Join through invitation: Receive an invite link from someone already in the network
- Browse or request: Search existing offerings or post what you're looking for
- Connect privately: Accept offers that interest you and start direct conversations
- Transact your way: Handle payments outside the platform (in person, Venmo, Cash, etc.)
- Build trust: Your network grows organically through real connections
Core Values
- Humans are eternal, the things we buy and sell are not.
- Relationships have lasting value, profit margins do not.
- Quality is worth far more than quantity.
Core Features
Request-Driven Commerce
The heart of our market is the request system. Users can post what they're looking for, and others on the platform can attempt to fulfill those requests. Whether you need a used stove for $500 or a custom software solution, simply describe what you are looking for and leverage your social connections to reduce the time and effort it takes to find it.
Three Item Categories
- New Items: Locally made goods from artisans and small businesses (custom crafts, 3D printed items, handmade clothing, etc.)
- Resale Items: Quality used goods that deserve a second life instead of going to waste (equipment, furniture, sporting goods, etc.)
- Services: Gig work and specialized skills (repairs, freelance work, custom shopping services, etc.)
Trust Through Connections
Our platform is invite-only during the beta phase. New accounts can only be created through invite links from existing members, automatically establishing a connection between inviter and invitee. This creates an organic network of people who know each other, ensuring authenticity from day one.
Privacy-First Design
Contact information (email and phone) has three visibility levels:
- Hidden: Only the system can see it
- Connections Only: Only visible to your direct connections
- Public: Visible to all users and visitors
When someone responds to your request, you see their offer first—not their contact details. You choose whether to open a direct messaging channel or decline with no further contact.
Technical Stack
- Frontend: Astro with React/Vue islands for interactive components
- Backend: Supabase (database, authentication, image storage)
- License: MIT
Project Status
The project is currently in early development. We're focused on validating the core marketplace mechanics with a trusted beta group before adding more complex features.
Contributing
This is an open-source project, and contributions are welcome. See our contributing guide to get started.
Documentation
For developers, see the developer documentation for architecture details and system documentation.