Skip to content

[#230] Add e-commerce store example app#255

Merged
milkyskies merged 42 commits intomainfrom
feature/#230.store-example
Mar 17, 2026
Merged

[#230] Add e-commerce store example app#255
milkyskies merged 42 commits intomainfrom
feature/#230.store-example

Conversation

@milkyskies
Copy link
Owner

closes #230

Summary

  • Adds a second, more complex example app at examples/store/ that exercises advanced Floe features not covered by the todo app
  • Uses DummyJSON API as a mock backend (no server needed)
  • TanStack Router + Query, React, Tailwind, Zod - same stack as the todo app

Language features showcased

Feature Where
Branded types (ProductId, OrderId) types.fl
Nested error unions (ApiError > NetworkError) types.fl, errors.fl
Multi-depth match (Network(Timeout(ms))) errors.fl
Range matching (400..499, 500..599) errors.fl
Traits (Display, Discountable) types.fl, product.fl
Tuples ((subtotal, discount, total)) product.fl, api.fl
Partial application (compareBy(order, _, _)) product.fl
Default parameter values api.fl (fetchProducts)
For-blocks with trait bounds product.fl, errors.fl
? operator for Result chaining api.fl
try await for async error handling api.fl
Inline match in JSX props catalog.fl, product-detail.fl
Pipe chains with dot shorthand product.fl, catalog.fl
tap / unreachable / match guards throughout

App structure

examples/store/src/
├── types.fl          # Branded types, nested unions, traits
├── api.fl            # DummyJSON API with Zod + Result<T, E>
├── product.fl        # For-blocks, cart ops, sorting, filtering
├── errors.fl         # Multi-depth error matching
├── store-context.tsx # React context for cart state
├── router.tsx        # TanStack Router setup
├── main.tsx          # Entry point
└── pages/
    ├── catalog.fl         # Product grid with filters/sorting
    ├── product-detail.fl  # Single product with reviews
    └── cart.fl            # Cart with order summary

Test plan

  • floe check examples/store/src/ passes
  • pnpm dev in examples/store/ loads the catalog
  • Clicking a product navigates to detail page
  • Add to cart works and badge updates
  • Cart page shows totals with discount calculation
  • Search and category filtering work

🤖 Generated with Claude Code

milkyskies and others added 30 commits March 16, 2026 12:15
Second example app showcasing advanced Floe features not covered by
the todo app: branded types (ProductId, OrderId), nested error unions
with multi-depth matching, range matching, tuples, traits, partial
application, and default parameter values. Uses DummyJSON as mock API
with TanStack Router + Query, React, Tailwind, and Zod.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use the Gleam/Rust pattern: `type ProductId = ProductId(number)` instead
of `Brand<number, "ProductId">`. Single-variant unions are a natural
newtype wrapper that reuses existing syntax (constructors, match, ==).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrites all .fl files to use the full vision of the language:
- Implicit returns (no return keyword)
- parse<T> built-in instead of Zod schemas
- Http.get/Http.json namespaced stdlib
- Pipe into match (value |> match { ... })
- Array pattern matching ([] and [first, ..rest])
- collect block for checkout validation
- Record type composition (...WithRating)
- deriving (Eq) on Product
- Number literal separators (10_000)
- Array.sum, Array.isEmpty stdlib additions
- Blank line before final expression (formatter convention)

This code won't pass floe check yet — it exercises features
from issues #257-#275 that haven't been implemented.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- product.fl: Replace Array.from with String.repeat, fix Array.sum by
  using Array.reduce with typed params, fix partial application
  placeholders with lambdas, use ProductId instead of number
- api.fl: Change fetchCategories return type to Array<CategoryResponse>
  to match checker inference, export CategoryResponse type
- checkout.fl: Replace validateCheckout(_, shipping) placeholder with
  direct call validateCheckout(cart, shipping)
- cart.fl: Use ProductId in callback types, remove unused imports,
  prefix unused variable with underscore
- catalog.fl: Replace tuple arrays with helper functions for price/sort
  buttons, fix operator precedence in comparisons, use parse<T> for
  trusted import data, fix sortProducts partial application
- product-detail.fl: Replace Array.from with String.repeat for stars,
  use parse<T> for trusted import data, wrap Array.map in div for
  consistent match arm types

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace PriceRange.Any and OrderStatus.Pending with unqualified
variants Any and Pending. Dot-based variant access doesn't work
reliably (will be replaced by :: syntax in #324).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace try await fetch() + Zod with Http.get |> Http.json |> parse<T>.
Remove Zod import entirely. Still has checker errors from #333 and #334.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Result uses { ok: true, value } / { ok: false, error }, not
{ tag: "Ok" } / { tag: "Err" }. Match codegen now checks
.ok === true/false and accesses .value/.error correctly.

Also fix Filter.All → All in todo app home.fl.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
const x = expr? now emits:
  const _r0 = expr;
  if (!_r0.ok) return _r0;
  const x = _r0.value;

Instead of the broken: const x = expr!

Also fix Result match codegen to use .ok/.error fields,
and fix destructured param codegen for { } and [ ] patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
milkyskies and others added 12 commits March 17, 2026 12:43
Instead of emitting deeply nested IIFEs with throw for chained pipe+unwrap
expressions like `await Http.get(url)? |> Http.json? |> parse<T>?`, flatten
the chain into sequential _rN temp variables with early return checks:

  const _r0 = await Http.get(...);
  if (!_r0.ok) return _r0;
  const _r1 = await Http.json(_r0.value);
  if (!_r1.ok) return _r1;
  const _r2 = parse(_r1.value);
  if (!_r2.ok) return _r2;
  const data = _r2.value;

Also auto-detects async stdlib IIFEs (e.g. Http.json) and adds await.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
props.selectedCategory == cat.slug |> match parsed as
props.selectedCategory == (cat.slug |> match) due to pipe
binding tighter than ==.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tuple (a, b) now emits [a, b] instead of [a, b] as const.
The as const caused [x, y] as const[0] to be parsed wrong by
esbuild, making tuple element access always return the array
instead of the element.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
data from useSuspenseQuery is already a Result — don't parse<T>
it again. Just match Ok/Err to unwrap the tuple value.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Vite plugin now passes file path to floe build instead of piping
  stdin, enabling cross-file import resolution
- Added cmd_build_file_stdout for --emit-stdout with a file path
- floe build now emits code even with checker warnings (prints to
  stderr instead of blocking). floe check still reports errors.
- Fixed store code: unwrap Results directly instead of re-parsing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The stdlib definition had (arr, initial, callback) but docs/usage
showed (arr, callback, initial). Fixed to match docs and JS convention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OrderId is a newtype wrapping number. In string interpolation,
access .value to display the number instead of [object Object].

TODO: compiler should auto-unwrap newtypes in string interpolation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Capture stderr from floe build instead of letting it print
directly. Checker warnings about unknown types from trusted
imports are noise — the code compiles and runs correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a Floe function (e.g. fetchProducts) is passed to a generic npm
function (e.g. useSuspenseQuery({ queryFn: async || fetchProducts() })),
tsgo couldn't infer the generic type because the function was undefined
in the probe file.

Two fixes:
1. In tsgo.rs, emit `declare function` stubs for all functions from
   resolved .fl imports, with proper parameter types and return types
   (including optional params and async/Promise wrapping).
2. In dts.rs, handle TSTypeOperatorType (readonly) by unwrapping to
   the inner type, fixing `readonly [T, U]` tuples being parsed as
   `unknown`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Confirmed(OrderId(n)) properly destructures the newtype via
multi-depth pattern matching, fixing checker error E006.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
emit_pattern_condition was always using `.value` for single-field
variant nested conditions, while collect_bindings correctly used
variant_info to resolve the actual field name (e.g. `orderId`).

This caused runtime errors like "Cannot read properties of undefined
(reading 'tag')" when matching Confirmed(OrderId(n)) because the
codegen emitted `.value.tag` instead of `.orderId.tag`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Root layout uses h-screen flex column so nav is pinned
- Main content area scrolls independently with overflow-y-auto
- Sidebar is sticky with self-start so it stays visible while
  products scroll

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@milkyskies milkyskies merged commit 3d71cde into main Mar 17, 2026
2 checks passed
@milkyskies milkyskies deleted the feature/#230.store-example branch March 17, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add e-commerce store example app

1 participant