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 .changeset/light-bars-visit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@lightsparkdev/lightspark-cli": patch
---

- Replace secp256k1 with @noble/curves: Same dependency swap — secp256k1 replaced with @noble/curves for key generation and validation. Removes native build
requirement.
8 changes: 8 additions & 0 deletions .changeset/spotty-yaks-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@lightsparkdev/core": patch
---

- Replace secp256k1 with @noble/curves: The native secp256k1 npm package (which requires native compilation/bindings) has been replaced with @noble/curves/secp256k1, a
pure JS implementation. This removes native build dependencies, improving portability and install reliability. Secp256k1SigningKey.sign() now uses @noble/curves API
internally (DER-encoded output is preserved).
- Add 3 new currencies: ZMW (Zambian Kwacha), AED (UAE Dirham), GTQ (Guatemalan Quetzal) — added to CurrencyUnit, conversion maps, formatting, and CurrencyMap type.
9 changes: 9 additions & 0 deletions .changeset/tender-jobs-call.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@lightsparkdev/ui": patch
---

- CodeInput: Clicking the unified code input now moves the cursor to the first empty input field instead of wherever the user clicked, improving OTP entry UX.
- CardForm: CardFormContentFull now sets width: 100% for proper full-width layout.
- CardPage: New headerRightContent prop to render content on the right side of the card page header.
- DataManagerTable filters: AppliedButtonsContainer extracted to a shared component with max-width: 100% on buttons (prevents overflow for long filter values).
- New icon: BankSolid icon added (exported as CentralBankSolid).
177 changes: 41 additions & 136 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,163 +1,68 @@
# CLAUDE.md
# JS Monorepo

This file provides guidance to Claude Code when working in the js/ directory.
TypeScript monorepo for Lightning Network SDKs and internal applications. Yarn workspaces + Turbo.

## Overview
## Quick Reference

JavaScript/TypeScript monorepo for Lightspark's Lightning Network and UMA services. Public SDKs, internal applications, and shared packages using Yarn workspaces + Turbo.
| Action | Command |
|--------|---------|
| Install | `yarn` |
| Start apps | `yarn start site uma-bridge ops` |
| Build | `yarn build` |
| Test | `yarn test` |
| Lint + format | `yarn lint && yarn format` |
| Full checks | `yarn checks` |
| GraphQL regen | `yarn gql-codegen` |
| Clean all | `yarn clean-all` |

## Structure

- **packages/** - Shared libraries
- **core/** - Auth, utilities
- **lightspark-sdk/** - Public Lightning SDK
- **ui/** - React components, design system
- **private/** - Internal utilities, GraphQL clients
- **apps/examples/** - Public examples and CLI tools
- **apps/private/** - Internal apps (site, ops, uma-bridge)

## Essential Commands

### Setup
```bash
nvm use || nvm install # Match Node version
corepack enable && corepack prepare --activate # Enable Yarn
yarn # Install all workspace dependencies
```

### Development Workflow
```bash
# Start applications
yarn start site uma-bridge ops # Specific apps (@lightsparkdev/ prefix implied)
yarn start private # All private apps
yarn start examples # All examples

# Code quality
yarn lint && yarn format # Lint + Prettier
yarn checks # Full validation: deps, lint, format, test, circular-deps

# Building
yarn build # Build all workspaces with Turbo caching
yarn build --force # Rebuild without cache

# Testing
yarn test # Run all tests
yarn workspace @lightsparkdev/ui test # Test specific workspace

# GraphQL codegen
yarn gql-codegen # Regenerate TypeScript types from schemas
packages/
core/ # Auth, utilities
lightspark-sdk/ # Public Lightning SDK
ui/ # React components
private/ # Internal utilities
apps/
examples/ # Public examples
private/ # Internal apps (site, ops, uma-bridge)
```

### Workspace Targeting
## Workspace Commands

```bash
# From repo root
# Target specific workspace
yarn workspace @lightsparkdev/<name> <command>

# From workspace directory
cd apps/private/uma-bridge && yarn start
# Examples
yarn workspace @lightsparkdev/uma-bridge start
yarn workspace @lightsparkdev/ui test
```

## Architecture & Patterns
## Code Patterns

### Monorepo Management
- **Yarn workspaces** with workspace protocol (`"@lightsparkdev/ui": "*"`)
- **Turbo** orchestrates builds with caching and parallelization
### Dependencies
- Use workspace protocol for internal deps: `"@lightsparkdev/ui": "*"`
- Shared configs: `@lightsparkdev/{tsconfig,eslint-config}`
- Build artifacts in `dist/`, ignored by git

### GraphQL
- TypeScript types auto-generated via GraphQL Code Generator
- Schema variants per API surface (internal, third-party)
- Fragments/operations defined per app
- Real-time subscriptions for transaction updates
- **After Python schema changes**: run `yarn gql-codegen` from root

### React Stack
- **Vite** - Dev server and bundler
- **Emotion** - CSS-in-JS styling
- **React Router** - Navigation
- **React Query** - Server state
- **Zustand** - Client state

### Testing
- **Jest** - Unit/integration tests
- **React Testing Library** - Component tests
- **Cypress** - E2E tests
- Tests colocated with source (`.test.ts`, `.spec.ts`)

## Configuration Files

- **turbo.json** - Build pipeline, task dependencies, caching
- **package.json** (root) - Workspace definitions, scripts
- **packages/eslint-config/** - Shared linting rules
- **packages/tsconfig/** - TypeScript presets

## Code Standards

- **TypeScript strict mode** enabled
- **ESLint** extends shared configs
- **Prettier** with import organization
- **Circular dependency detection** via madge
- **Prefer Edit tool** over inline rewrites for existing code

## Common Task Workflows

### Adding New Package
1. Create directory in `packages/` or `apps/`
2. Add workspace reference in root `package.json`
3. Create `package.json` with dependencies (use workspace protocol for internal deps)
4. Add build config to `turbo.json` if needed
5. Run `yarn` to link workspace

### GraphQL Type Updates
After Python backend schema changes:
```bash
yarn gql-codegen # All workspaces
yarn workspace @lightsparkdev/uma-bridge gql-codegen # Specific app
```

### Debugging Build Issues
```bash
yarn clean-all # Remove dist/ and caches
yarn build --force # Bypass Turbo cache
yarn clean-resolve # Nuclear option: reset lockfile
```

### UMA Bridge Development
After Python schema changes:
```bash
# Lint bridge + UI package
yarn run lint --filter=@lightsparkdev/ui --filter=@lightsparkdev/uma-bridge

# Start with Vite dev server (proxy configured for backend)
yarn workspace @lightsparkdev/uma-bridge start
yarn gql-codegen # All workspaces
yarn workspace @lightsparkdev/uma-bridge gql-codegen # Specific
```

Integrates with: Plaid, Tazapay, Striga, other payment providers

### Adding Dependencies
```bash
# Workspace-specific
yarn workspace @lightsparkdev/<name> add <package>

# Root-level (affects all workspaces)
yarn add -W <package>
yarn workspace @lightsparkdev/<name> add <package> # To workspace
yarn add -W <package> # Root level
```

## Release Process

**Public packages** (SDK, core utilities):
- Changesets for version management
- Copybara syncs to public repo on main merge
- Release PR auto-created
- Merge Release PR → npm publish

**Private packages** (`packages/private/`, `apps/private/`):
- Not published to npm
- Versioned internally only

## Troubleshooting

**Import errors**: Check workspace dependencies use `"*"` not version numbers
**Type errors after GraphQL changes**: Run `yarn gql-codegen`
**Stale build artifacts**: `yarn clean-all && yarn build`
**Turbo cache issues**: Add `--force` flag to bypass cache
| Issue | Fix |
|-------|-----|
| Import errors | Check deps use `"*"` not versions |
| Type errors after GraphQL | `yarn gql-codegen` |
| Stale builds | `yarn clean-all && yarn build` |
| Cache issues | `yarn build --force` |
65 changes: 63 additions & 2 deletions apps/examples/ui-test-app/src/tests/CodeInput.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { jest } from "@jest/globals";
import { CodeInput } from "@lightsparkdev/ui/components/CodeInput/CodeInput";
import { fireEvent, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { render } from "./render";

describe("CodeInput", () => {
beforeEach(() => {
const mockClipboardReadWithoutNumbers = jest.fn(() =>
Promise.resolve("sdkjfnsd"),
);
Object.assign(navigator, {
clipboard: {
// `userEvent.setup()` may install `navigator.clipboard` as a getter-only prop.
// Use `defineProperty` so this mock is resilient regardless of that setup.
Object.defineProperty(navigator, "clipboard", {
value: {
readText: mockClipboardReadWithoutNumbers,
},
configurable: true,
});
});

Expand Down Expand Up @@ -134,4 +138,61 @@ describe("CodeInput", () => {
expect(inputFields[3]).toHaveValue(null);
expect(inputFields[2]).toHaveFocus();
});

it("redirects focus to first empty input when clicking on empty input in unified variant", async () => {
const user = userEvent.setup();
render(<CodeInput codeLength={6} variant="unified" />);
const inputFields = screen.getAllByRole("textbox");
expect(inputFields).toHaveLength(6);

// Enter some digits in the first two positions
fireEvent.keyDown(inputFields[0], { key: "1" });
fireEvent.keyDown(inputFields[1], { key: "2" });

// Now focus should be on the third input (index 2)
expect(inputFields[2]).toHaveFocus();

// Simulate clicking on the 5th input (index 4) - an empty position
// onMouseDown should redirect focus to the first empty input (index 2)
await user.click(inputFields[4]);
expect(inputFields[2]).toHaveFocus();
});

it("allows clicking on any filled input in unified variant", async () => {
const user = userEvent.setup();
render(<CodeInput codeLength={6} variant="unified" />);
const inputFields = screen.getAllByRole("textbox");

// Enter some digits
fireEvent.keyDown(inputFields[0], { key: "1" });
fireEvent.keyDown(inputFields[1], { key: "2" });
fireEvent.keyDown(inputFields[2], { key: "3" });

// Focus should be on the 4th input (index 3)
expect(inputFields[3]).toHaveFocus();

// Clicking on a filled input (index 1) should work normally - no redirect
await user.click(inputFields[1]);
expect(inputFields[1]).toHaveFocus();

// Can also click on index 0
await user.click(inputFields[0]);
expect(inputFields[0]).toHaveFocus();

// Can also click on index 2
await user.click(inputFields[2]);
expect(inputFields[2]).toHaveFocus();
});

it("focuses first input when all inputs are empty in unified variant", () => {
render(<CodeInput codeLength={6} variant="unified" />);
const inputFields = screen.getAllByRole("textbox");

// Blur the auto-focused first input
fireEvent.blur(inputFields[0]);

// Click on a middle input when all are empty
fireEvent.mouseDown(inputFields[3]);
expect(inputFields[0]).toHaveFocus();
});
});
3 changes: 1 addition & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@
},
"license": "Apache-2.0",
"dependencies": {
"@noble/curves": "^1.9.7",
"dayjs": "^1.11.7",
"graphql": "^16.6.0",
"graphql-ws": "^5.11.3",
"secp256k1": "^5.0.1",
"ws": "^8.12.1",
"zen-observable-ts": "^1.1.0"
},
Expand All @@ -77,7 +77,6 @@
"@types/crypto-js": "^4.1.1",
"@types/jest": "^29.5.3",
"@types/lodash-es": "^4.17.6",
"@types/secp256k1": "^4.0.3",
"@types/ws": "^8.5.4",
"auto-bind": "^5.0.1",
"eslint": "^8.3.0",
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/crypto/SigningKey.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import secp256k1 from "secp256k1";
import { secp256k1 } from "@noble/curves/secp256k1";
import { createSha256Hash } from "../utils/createHash.js";
import { hexToBytes } from "../utils/hex.js";
import type { CryptoInterface } from "./crypto.js";
Expand Down Expand Up @@ -46,7 +46,7 @@ export class Secp256k1SigningKey extends SigningKey {
async sign(data: Uint8Array) {
const keyBytes = new Uint8Array(hexToBytes(this.privateKey));
const hash = await createSha256Hash(data);
const signResult = secp256k1.ecdsaSign(hash, keyBytes);
return secp256k1.signatureExport(signResult.signature);
const sig = secp256k1.sign(hash, keyBytes);
return sig.toBytes("der");
}
}
Loading