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
7 changes: 4 additions & 3 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [
"classy-store-angular-example",
"classy-store-nextjs-example",
"classy-store-react-example",
"classy-store-vue-example",
"classy-store-svelte-example",
"classy-store-solid-example",
"classy-store-angular-example"
"classy-store-svelte-example",
"classy-store-vue-example"
]
}
6 changes: 6 additions & 0 deletions .changeset/dry-islands-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@codebelt/classy-store": patch
---

- ensure paused state update is deferred using queueMicrotask in history utils
- prevent arrow functions bypassing proxy traps, add error isolation for listeners
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Enforced by Biome 2.4.0 (`biome.json` at repo root):

## Key Technical Facts

- **No arrow function methods:** Arrow function properties (`increment = () => {}`) have `this` lexically bound to the raw instance. `.bind()` is a no-op on arrow functions, so the GET trap cannot rebind `this` to the proxy. Mutations inside arrow functions bypass the SET trap entirely — no notifications, no reactivity. Always use prototype methods (`increment() {}`).
- **Batching:** mutations are coalesced via `queueMicrotask`. Multiple synchronous writes (including array `push` which triggers multiple SET traps) produce a single subscriber notification.
- **Internal state:** stored in a `WeakMap<proxy, StoreInternal>` — never on the user's object. Allows GC when a store is dereferenced.
- **Non-proxyable types:** `Date`, `RegExp`, native `Map`, and native `Set` are treated as opaque values (internal slots can't be intercepted by Proxy). Use `reactiveMap()` and `reactiveSet()` for Map/Set semantics. Replace Date instances entirely to trigger updates.
Expand Down
256 changes: 248 additions & 8 deletions bun.lock

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions examples/nextjs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
22 changes: 22 additions & 0 deletions examples/nextjs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# @codebelt/classy-store Next.js Example

This is a Next.js example project demonstrating the usage of `@codebelt/classy-store` within a monorepo setup.

## Getting Started

First, run the development server:

```bash
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.


- Realtime Colors (realtimecolors.com) — great for light/dark pairs
- Coolors (coolors.co) — shareable palette URLs
- Tailwind Color Generator (uicolors.app)
- Radix Colors (radix-ui.com/colors)
- Shadcn Themes (ui.shadcn.com/themes)
- Happy Hues (happyhues.co)
- Color Hunt (colorhunt.co)
9 changes: 9 additions & 0 deletions examples/nextjs/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type {NextConfig} from 'next';

const nextConfig: NextConfig = {
/* config options here */
reactCompiler: true,
transpilePackages: ['@codebelt/classy-store'],
};

export default nextConfig;
33 changes: 33 additions & 0 deletions examples/nextjs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "classy-store-nextjs-example",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "bun --bun next dev",
"build": "bun --bun next build",
"start": "bun --bun next start"
},
"dependencies": {
"@codebelt/classy-store": "workspace:*",
"next": "16.1.6",
"react": "19.2.3",
"react-dom": "19.2.3"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"babel-plugin-react-compiler": "1.0.0",
"tailwindcss": "^4",
"typescript": "^5"
},
"ignoreScripts": [
"sharp",
"unrs-resolver"
],
"trustedDependencies": [
"sharp",
"unrs-resolver"
]
}
7 changes: 7 additions & 0 deletions examples/nextjs/postcss.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
};

export default config;
Binary file added examples/nextjs/src/app/favicon.ico
Binary file not shown.
25 changes: 25 additions & 0 deletions examples/nextjs/src/app/features/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {FeatureMap} from '@/components/features/FeatureMap';

export default function FeaturesPage() {
return (
<div className="space-y-12 animate-fade-in pb-20">
{/* Hero Section */}
<section className="relative">
<div className="absolute -top-20 -left-20 w-96 h-96 bg-purple-500/10 blur-3xl rounded-full pointer-events-none" />
<div className="relative z-10">
<h1 className="text-4xl md:text-5xl font-bold tracking-tight text-white mb-3">
Feature Map
</h1>
<p className="text-lg text-muted-foreground max-w-2xl leading-relaxed">
A comprehensive index of every classy-store API used in this demo,
with direct links to the implementation.
</p>
</div>
</section>

<section>
<FeatureMap />
</section>
</div>
);
}
207 changes: 207 additions & 0 deletions examples/nextjs/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
@import "tailwindcss";

@theme {
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);

--color-background: var(--background);
--color-foreground: var(--foreground);

--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);

--color-card: var(--card);
--color-card-foreground: var(--card-foreground);

--color-border: var(--border);
--color-input: var(--input);

--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);

--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);

--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);

--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);

/* Radii */
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);

/* Animations */
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-fade-in: fade-in 0.5s cubic-bezier(0.4, 0, 0.2, 1);
--animate-slide-up: slide-up 0.4s cubic-bezier(0.4, 0, 0.2, 1);
--animate-pulse-subtle: pulse-subtle 3s infinite ease-in-out;

@keyframes accordion-down {
from {
height: 0;
}

to {
height: var(--radix-accordion-content-height);
}
}

@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}

to {
height: 0;
}
}

@keyframes fade-in {
from {
opacity: 0;
}

to {
opacity: 1;
}
}

@keyframes slide-up {
from {
transform: translateY(10px);
opacity: 0;
}

to {
transform: translateY(0);
opacity: 1;
}
}

@keyframes pulse-subtle {
0%,
100% {
opacity: 1;
}

50% {
opacity: 0.7;
}
}
}

/* Base Variables - Premium Dark Theme */
/* Base Variables - Premium Dark Theme */
:root {
--background: #0a0a0a;
--foreground: #ffffff;
/* Improved to pure white for max contrast */

--card: #141414;
--card-foreground: #ffffff;

--popover: #0a0a0a;
--popover-foreground: #ffffff;

/* Vibrant Orange Primary - Shifted to Orange-400 for better WCAG on black (5.3:1) */
--primary: #fb923c;
/* Dark text on bright orange button (10.5:1 Contrast - AAA) */
--primary-foreground: #09090b;

--secondary: #27272a;
--secondary-foreground: #ffffff;

--muted: #27272a;
/* Lighter gray for better readability on dark bg (Zinc-400 is ~5.8:1) */
--muted-foreground: #a1a1aa;

--accent: #27272a;
--accent-foreground: #ffffff;

/* Darker Red for better contrast with white text */
--destructive: #ef4444;
--destructive-foreground: #ffffff;

--border: #27272a;
--input: #27272a;
/* Match primary */
--ring: #fb923c;

--radius: 0.75rem;
}

/* Global Reset & Body Styles */
@layer base {
* {
@apply border-border;
}

body {
@apply bg-background text-foreground antialiased;
font-feature-settings:
"rlig" 1,
"calt" 1;
/* Subtle mesh gradient background */
background-image:
radial-gradient(at 0% 0%, rgba(249, 115, 22, 0.08) 0px, transparent 50%),
radial-gradient(at 100% 0%, rgba(59, 130, 246, 0.05) 0px, transparent 50%);
background-attachment: fixed;
}
}

/* Custom Components / Utilities */
@layer components {
/* Glassmorphism Card Style */
.glass-card {
@apply bg-card/60 backdrop-blur-md border border-white/10 shadow-lg rounded-xl transition-all duration-300;
}

.glass-card:hover {
@apply bg-card/80 border-white/20 shadow-xl scale-[1.01];
}

/* Text Gradients */
.text-gradient {
@apply bg-clip-text text-transparent bg-gradient-to-r from-white via-white to-gray-400;
}

.text-gradient-primary {
@apply bg-clip-text text-transparent bg-gradient-to-r from-orange-400 to-amber-600;
}

/* Animations */
.animate-appear {
animation: fade-in 0.6s ease-out forwards;
}

.animate-slide-up-delay-1 {
animation: slide-up 0.5s ease-out 0.1s forwards;
opacity: 0;
/* Important for delay */
}

.animate-slide-up-delay-2 {
animation: slide-up 0.5s ease-out 0.2s forwards;
opacity: 0;
}

.animate-slide-up-delay-3 {
animation: slide-up 0.5s ease-out 0.3s forwards;
opacity: 0;
}
}

@layer utilities {
/*
* Semantic fix: The codebase uses `text-muted` intending "muted text color" (readable gray),
* but by default `text-muted` maps to the theme color `muted` (dark gray background).
* We override it here to ensuring readability on dark backgrounds.
*/
.text-muted {
color: var(--muted-foreground);
}
}
Loading