Conversation
Pre-integration baseline. Adds the dashmint-lab React/Vite NFT app under example-apps/ as an isolated subproject.
- Repoint src/dash/{client,keyManager}.ts to import setupDashClient-core from the repo root; drop the vendored copy under src/dash/vendor/
- Rename package name and localStorage key from nft-modern to dashmint-lab
- Pin @dashevo/evo-sdk to the host's exact prerelease version
- Exclude example-apps/ from host tsc/prettier/eslint; add Vite artifact patterns to .gitignore
- Add example-apps/README.md index and link from the root README
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire up vitest (config, script, dev dep) and add coverage for normalizeCards, formatCredits, rarityOf, and withAuthedCard. Alongside the tests: - withAuthedCard now rethrows errors instead of swallowing them as undefined, so callers and tests can observe failures. - useDpnsName reads directly from the module cache and uses a render bump instead of mirroring cache state in useState, removing the stale-name flash. - Add .js extensions to relative type imports for NodeNext resolution. - Minor contract.ts comment cleanup on NFT flag semantics. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- add a typed declaration for the shared setupDashClient core boundary - introduce narrow local SDK/key-manager wrapper types for the app - replace learner-facing any usage across session, dash wrappers, and SDK-bearing component props - add inline success and error notices to transfer, price, purchase, and burn modals - keep toast notifications while preventing modal close on mutation failure - add representative modal behavior coverage with vitest and testing-library - document testnet and bundle constraints in the README
Covers App prop wiring and refresh callbacks, SessionProvider state transitions, all mutation modals, MintForm flows, and withAuthedCard revision edge cases. Guards the undefined-revision case in withAuthedCard now that a test pins the behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds CardTile unit tests for the owner/buyer/browse action-button matrix, overflow menu gating, and DPNS fallback. Adds App tests for the mint-screen overlays across browsing, non-owner, and owner sessions, verifying the login/settings buttons open the LoginModal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Classify the recipient input by character set (not length) so the field accepts `alice.dash`, `Alice`, or a raw identity ID interchangeably. Name inputs resolve via `sdk.dpns.resolveName`; ambiguous inputs try DPNS and fall back to treating the string as an ID. Disable Transfer while a name is resolving or not-found, and never submit a typed name as a raw ID. Make truncated identity IDs clickable to Platform Explorer via a new shared `src/lib/explorer.ts` helper (also adopted by CardTile). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace three magic 100/50 defaults with a single MAX_QUERY_LIMIT constant in queries.ts. Also raises listAllCards / listMarketplaceCards from 50 to 100 (the platform max) for consistency with listMyCards. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract IdentityLink into a shared component and wrap the owner chip label (@alice or truncated ID) on every card so clicking opens the owner's identity page on testnet.platform-explorer.com in a new tab. TransferModal now uses the same shared component for its resolved recipient link. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a "Balance · X credits" chip to the top-right of the page heading.
Balance lives in SessionContext and is fetched via sdk.identities.balance
on login and on every mutation via the existing refresh signal; cleared
on logout and browseOnly so it never leaks into anonymous mode.
Also hardens test/App.test.tsx against a pre-existing race where
getByTestId("cards") was read synchronously after awaiting only the
query mock's call (not the state update).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract starter cards into src/data/starterPack.ts with an 8-card pool and drawStarterPack helper that returns STARTER_PACK_SIZE random cards. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… base path - Manual-trigger workflow publishes the built SPA under /<repo>/dashmint-lab/ - Base path is derived from github.event.repository.name so forks work without editing the workflow - Alias @dashevo/evo-sdk in vite.config.ts to the app's installed copy, so the shared browser-safe core imported from the repo root resolves without a separate root-level npm install Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- derive theme and composition from card name, description, stats, rarity, and id - give cards stronger visual identity with layered backgrounds, silhouettes, and rarity effects - add focused tests for theme resolution and deterministic rendering - document the art helper as presentation-only so tutorial readers can ignore it when learning Platform flows
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds a new standalone example app "dashmint-lab" (React + TypeScript + Vite) with SDK integrations, session management, NFT card operations (mint/transfer/set price/purchase/burn), supporting utilities, comprehensive tests, CI/CD workflow for GitHub Pages, and root/app-level ignore/config updates. Changes
Sequence Diagram(s)sequenceDiagram
participant User as Client (UI)
participant Session as SessionProvider
participant Auth as IdentityKeyManager
participant SDK as DashSdk
participant Backend as Dash Documents
rect rgba(200,230,255,0.5)
User->>Session: open LoginModal & submit mnemonic
Session->>Auth: IdentityKeyManager.create(mnemonic)
Auth-->>Session: auth (identityId, identityKey, signer)
Session->>SDK: createClient / set auth context
SDK-->>Session: connected client
Session-->>User: status "authenticated"
end
rect rgba(200,255,200,0.5)
User->>Session: click "Mint" (submit form)
Session->>SDK: withAuthedCard -> sdk.documents.create(document, identityKey, signer)
SDK->>Backend: documents.create(...)
Backend-->>SDK: document created
SDK-->>Session: success
Session-->>User: show success notice + refresh lists
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 16
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (13)
example-apps/dashmint-lab/src/lib/explorer.ts-1-1 (1)
1-1:⚠️ Potential issue | 🟡 MinorUse single quotes to match repository Prettier rules.
Line 1 uses double quotes and should be single-quoted to match the configured style.
As per coding guidelines "
**/*.{js,mjs,ts,tsx,json,md}: Code must be formatted with Prettier using single quotes, 2-space tabs, and trailing commas".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/lib/explorer.ts` at line 1, The EXPLORER_BASE constant in explorer.ts uses double quotes; update the declaration of EXPLORER_BASE to use single quotes to comply with the repository Prettier rules (single quotes, 2-space indentation, trailing commas) so change the string delimiter for the constant EXPLORER_BASE from "https://testnet.platform-explorer.com" to use single quotes.example-apps/dashmint-lab/test/resolveRecipient.test.ts-9-15 (1)
9-15:⚠️ Potential issue | 🟡 MinorNon-string error path is not actually tested.
Line 60 says non-string (e.g.,
undefined), but the mock still returnsnull(same case as Line 53). This leaves the non-string guard unverified.✅ Suggested test fix
-function sdkWith(resolve: (name: string) => Promise<string | null>): DashSdk { +function sdkWith(resolve: (name: string) => Promise<unknown>): DashSdk { return { dpns: { resolveName: vi.fn(resolve), username: vi.fn(), }, } as unknown as DashSdk; } @@ it("throws when the SDK returns a non-string (e.g. undefined)", async () => { - const sdk = sdkWith(async () => null); + const sdk = sdkWith(async () => undefined); await expect(resolveDpnsName(sdk, "nobody")).rejects.toThrow( /No identity found/, ); });Also applies to: 60-64
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/test/resolveRecipient.test.ts` around lines 9 - 15, The test never exercises the non-string error branch because sdkWith's dpns.resolveName mock returns null instead of a non-string value; update the failing test to have sdkWith(...) supply a resolve implementation that returns undefined (or another non-string) for the non-string path so the guard in the code is exercised, e.g., change the resolveName mock used in the "non-string" case to return Promise.resolve(undefined) and assert the expected error/handling; look for the sdkWith helper and tests that call it and modify the resolve implementation for the non-string scenario.example-apps/dashmint-lab/src/dash/logger.ts-8-11 (1)
8-11:⚠️ Potential issue | 🟡 MinorDoc comment level count is inaccurate.
The comment says “four levels,” but
LogLeveldefines three (info | success | error). Please align the comment with the actual type.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/dash/logger.ts` around lines 8 - 11, The doc comment incorrectly says "four levels" while the exported type LogLevel only defines three values ("info" | "success" | "error"); update the comment to reflect "three levels" (or otherwise reword/remove the numeric claim) so the comment matches the LogLevel declaration in logger.ts.example-apps/dashmint-lab/src/dash/resolveRecipient.ts-24-27 (1)
24-27:⚠️ Potential issue | 🟡 MinorReject empty recipient names before appending
.dash.Whitespace-only input currently becomes
.dash, which leads to a misleading lookup path. Validate non-empty normalized input first and throw a user-facing error.🔧 Proposed fix
export function normalizeDpnsName(input: string): string { const lower = input.trim().toLowerCase(); + if (!lower) { + throw new Error('Recipient name cannot be empty'); + } return lower.endsWith(".dash") ? lower : `${lower}.dash`; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/dash/resolveRecipient.ts` around lines 24 - 27, The normalizeDpnsName function currently converts whitespace-only input into ".dash"; update normalizeDpnsName to trim and lower-case the input, then check that the resulting name is non-empty and not just dots before appending ".dash" — if it is empty after normalization throw a clear, user-facing error (e.g., "Recipient name cannot be empty") so callers get a proper validation failure instead of a misleading lookup path.example-apps/dashmint-lab/src/components/Modal.tsx-41-47 (1)
41-47:⚠️ Potential issue | 🟡 MinorSet an explicit button type on the close button.
Without
type="button", this control can submit a parent form unintentionally.Proposed fix
<button + type="button" onClick={onClose} className="text-ink-4 transition hover:text-ink" aria-label="Close" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/components/Modal.tsx` around lines 41 - 47, The close button in Modal.tsx currently lacks an explicit type which can cause it to submit a surrounding form; update the close button element (the button with onClick={onClose} and aria-label="Close") to include type="button" so it behaves as a non-submitting control and preserves the onClose handler behavior.example-apps/dashmint-lab/package.json-17-17 (1)
17-17:⚠️ Potential issue | 🟡 MinorMove
@tailwindcss/vitetodevDependencies.
@tailwindcss/viteis a Vite plugin used only invite.config.tsfor build configuration and should not be in runtime dependencies.Proposed manifest change
"dependencies": { "@dashevo/evo-sdk": "3.1.0-dev.1", - "@tailwindcss/vite": "^4.2.2", "react": "^19.2.4", "react-dom": "^19.2.4", "sonner": "^2.0.7", "tailwindcss": "^4.2.2" }, "devDependencies": { + "@tailwindcss/vite": "^4.2.2", "@eslint/js": "^9.39.4",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/package.json` at line 17, The package.json currently lists "@tailwindcss/vite" as a regular dependency but it is only used at build time in vite.config.ts; move the entry for "@tailwindcss/vite" from "dependencies" to "devDependencies" in package.json so it is not installed at runtime, and ensure any import or usage in vite.config.ts remains unchanged (e.g., references to the plugin name in the Vite config).example-apps/dashmint-lab/src/dash/transferCard.ts-31-40 (1)
31-40:⚠️ Potential issue | 🟡 MinorValidate a trimmed recipient ID, not just truthiness.
At Line 31, a whitespace-only
recipientIdpasses the guard and is sent at Line 39. Trim first and validate the normalized value.Proposed fix
- if (!recipientId) throw new Error("Recipient identity ID is required."); - log?.(`Transferring card ${cardId} to ${recipientId}…`); + const normalizedRecipientId = recipientId.trim(); + if (!normalizedRecipientId) { + throw new Error("Recipient identity ID is required."); + } + log?.(`Transferring card ${cardId} to ${normalizedRecipientId}…`); @@ - recipientId, + recipientId: normalizedRecipientId,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/dash/transferCard.ts` around lines 31 - 40, Normalize and validate recipientId by trimming whitespace before the truthiness check and usage: replace the current guard with const normalizedRecipientId = recipientId?.trim(); if (!normalizedRecipientId) throw new Error("Recipient identity ID is required."); then use normalizedRecipientId in the log call and pass normalizedRecipientId to sdk.documents.transfer inside the withAuthedCard callback (referencing recipientId, sdk.documents.transfer, and withAuthedCard).example-apps/dashmint-lab/CLAUDE.md-16-18 (1)
16-18:⚠️ Potential issue | 🟡 MinorClarify the
src/dash/rule here.This says operation files have “no wrappers,” but later sections require
withAuthedCardfor transfer/set-price/purchase. That contradiction will send contributors in the wrong direction about whether the revision/auth wrapper is expected.Also applies to: 37-42
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/CLAUDE.md` around lines 16 - 18, The doc line claiming "no wrappers" for files under src/dash/ conflicts with later requirements for using the withAuthedCard wrapper for certain operations; update the README text to clarify that most SDK operation files are plain functions (see classifyRecipientInput and resolveRecipient helpers) but that operations which require authenticated card context (e.g., transfer, set-price, purchase) must export or call through the withAuthedCard wrapper; explicitly list/point to those functions and the withAuthedCard usage as exceptions so contributors know when to apply the auth wrapper.example-apps/dashmint-lab/src/dash/mintCard.ts-45-46 (1)
45-46:⚠️ Potential issue | 🟡 MinorValidate stat overrides before creating the document.
attackanddefenseoverrides are accepted verbatim here, so callers can pass0,11, or non-integers and either hit contract validation errors or break the app's rarity assumptions.Suggested fix
const attack = card.attack ?? rollStat(); const defense = card.defense ?? rollStat(); + for (const [label, value] of [ + ["attack", attack], + ["defense", defense], + ] as const) { + if (!Number.isInteger(value) || value < 1 || value > 10) { + throw new Error(`Card ${label} must be an integer between 1 and 10.`); + } + } const description = card.description?.trim();Based on learnings, "Attack/defense values are randomly generated (1–10) on mint; rarity must be derived client-side in src/lib/rarity.ts (common ≤10, rare 11–14, legendary ≥15) and is not persisted".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/dash/mintCard.ts` around lines 45 - 46, Validate any provided attack/defense overrides in the mint flow: in the code that sets attack and defense (the lines using rollStat()), check card.attack and card.defense and only accept them if they are integers between 1 and 10 (use Number.isInteger or equivalent); otherwise ignore the override and call rollStat() to generate the value. Update mintCard.ts (the attack/defense assignment logic) to perform this validation so non-integers or out-of-range values (e.g., 0, 11, floats) cannot be persisted or used, and leave rarity derivation to the client-side logic in src/lib/rarity.ts (do not persist rarity). Ensure references to rollStat(), attack, and defense are the places you change.example-apps/dashmint-lab/src/components/IdentityCard.tsx-69-108 (1)
69-108:⚠️ Potential issue | 🟡 MinorGive the browse-only button an action label.
When
status === 'browsing', this renders a clickable button whose label is effectively just “Connected”, even though it opens the login/settings flow. Add visible sign-in text or anaria-labelthat describes the action.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/components/IdentityCard.tsx` around lines 69 - 108, The button in IdentityCard.tsx uses onLoginClick but when the user is in browse-only mode (status === 'browsing' / equivalently !isAuthed) its visible label is only "Connected", which is misleading; update the <button> element (and/or the “Connected” span) to provide an explicit action label by adding an aria-label like "Open sign-in or settings" when !isAuthed (or status === 'browsing'), and/or render visible sign-in text (e.g., "Sign in" or "Open account") next to the Connected label conditionally based on isAuthed, ensuring accessibility attributes and text use the existing onLoginClick handler and do not change the click behavior.example-apps/dashmint-lab/src/session/SessionContext.tsx-161-223 (1)
161-223:⚠️ Potential issue | 🟡 MinorClear stale
erroron successful state transitions.After a failed login/connect, a later successful
login(),browseOnly(), orlogout()leaves the previous error message in context. That makessession.errorunreliable for the rest of the UI.Suggested fix
const login = useCallback( async (mnemonic: string, identityIndex = 0) => { const trimmed = mnemonic.trim(); if (!trimmed) throw new Error("Mnemonic is required."); + setError(null); try { const connected = sdk ?? (await connect()); log("Deriving identity keys from mnemonic…"); const km = await IdentityKeyManager.create({ @@ const browseOnly = useCallback(async () => { try { + setError(null); if (!sdk) await connect(); setKeyManager(null); setIdentityId(null); setBalance(null); setStatus("browsing"); @@ const logout = useCallback(() => { + setError(null); setKeyManager(null); setIdentityId(null); setBalance(null); setStatus(sdk ? "browsing" : "idle"); log("Logged out.", "info");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/session/SessionContext.tsx` around lines 161 - 223, After successful state transitions clear any previous error by calling setError(null): in the login function (inside the try block after identity is set and before/when setStatus("authenticated") is called) add setError(null); in browseOnly (inside the try block after successful connection and before setStatus("browsing")) add setError(null); and in logout add setError(null) alongside the existing state clears. Reference functions/identifiers: login, browseOnly, logout, setError, setStatus, setKeyManager, setIdentityId, setBalance.example-apps/dashmint-lab/src/session/SessionContext.tsx-171-184 (1)
171-184:⚠️ Potential issue | 🟡 MinorValidate
identityIndexin the session API.
login()accepts a publicidentityIndexand passes it through unchanged.min={0}in the modal is not enough here; a negative or non-integer value can still reachIdentityKeyManager.create(). Clamp/sanitize it before use.Suggested fix
async (mnemonic: string, identityIndex = 0) => { const trimmed = mnemonic.trim(); if (!trimmed) throw new Error("Mnemonic is required."); try { const connected = sdk ?? (await connect()); + const safeIdentityIndex = + Number.isFinite(identityIndex) + ? Math.max(0, Math.trunc(identityIndex)) + : 0; log("Deriving identity keys from mnemonic…"); const km = await IdentityKeyManager.create({ sdk: connected, mnemonic: trimmed, network: "testnet", - identityIndex, + identityIndex: safeIdentityIndex, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/session/SessionContext.tsx` around lines 171 - 184, The login function currently passes the public identityIndex straight through to IdentityKeyManager.create; sanitize it first by coercing to an integer and clamping to a non-negative value (e.g., compute a safeIndex = Math.max(0, Math.floor(Number(identityIndex || 0)))) and use safeIndex in the call to IdentityKeyManager.create, and optionally throw a clear error if identityIndex is NaN/invalid before attempting creation.example-apps/dashmint-lab/test/App.test.tsx-3-584 (1)
3-584:⚠️ Potential issue | 🟡 MinorRun Prettier for quote style compliance.
This file is consistently using double quotes, but repo formatting requires single quotes (plus standard trailing commas/2-space indentation). Please reformat before merge to avoid formatting-check failures.
As per coding guidelines,
**/*.{js,mjs,ts,tsx,json,md}: Code must be formatted with Prettier using single quotes, 2-space tabs, and trailing commas.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/test/App.test.tsx` around lines 3 - 584, The file fails repo Prettier rules (uses double quotes and inconsistent trailing commas/indentation); run Prettier with the repo config to convert to single quotes, 2-space indentation and add trailing commas. Reformat the test file (symbols to locate: makeSession, cards, all vi.mock blocks and describe("App") tests) or run the project-wide Prettier command (e.g., npm/yarn prettier --write) so the import strings, JSX attributes, and object literals use single quotes and conform to trailing-comma and 2-space rules before merging.
🧹 Nitpick comments (14)
example-apps/dashmint-lab/src/lib/explorer.ts (1)
3-9: Optional: encode path params before building explorer URLs.If malformed/unsafe IDs ever reach this helper, raw interpolation can produce broken URLs. Encoding makes links resilient.
Suggested hardening
-const EXPLORER_BASE = "https://testnet.platform-explorer.com"; +const EXPLORER_BASE = 'https://testnet.platform-explorer.com'; export function identityUrl(identityId: string): string { - return `${EXPLORER_BASE}/identity/${identityId}`; + return `${EXPLORER_BASE}/identity/${encodeURIComponent(identityId)}`; } export function documentUrl(documentId: string): string { - return `${EXPLORER_BASE}/document/${documentId}`; + return `${EXPLORER_BASE}/document/${encodeURIComponent(documentId)}`; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/lib/explorer.ts` around lines 3 - 9, The identityUrl and documentUrl helpers currently interpolate raw IDs into paths (identityUrl and documentUrl using EXPLORER_BASE), which can produce broken/unsafe links; update both functions to encode the path parameters with encodeURIComponent (or a project-wide safe encoder) before concatenating with EXPLORER_BASE so malformed or unsafe identityId/documentId values produce valid URLs.example-apps/dashmint-lab/src/styles/globals.css (1)
1-1: Stylelint import-notation: consider using string notation.Stylelint flags this as preferring string notation over
url(). If your project enforces this rule, update accordingly.Proposed fix
-@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap"); +@import "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/styles/globals.css` at line 1, The `@import` at-rule currently uses url(...) notation which Stylelint's import-notation rule flags; replace the url(...) form with string notation by changing `@import` url("https://fonts.googleapis.com/...") to `@import` "https://fonts.googleapis.com/..."; update the `@import` statement that references the Inter and JetBrains Mono fonts so it uses a quoted string instead of url(), preserving the same URL and quotes.example-apps/dashmint-lab/src/lib/format.ts (2)
17-21: Type signature doesn't includenull, but runtime check does.The parameter type is
number | bigint | undefined, yet line 18 also checks fornull. Either addnullto the type for accuracy, or remove the redundant check if strict TypeScript ensuresnullnever reaches here.Option A: Include null in type
-export function formatCredits(price: number | bigint | undefined): string { +export function formatCredits(price: number | bigint | null | undefined): string {Option B: Remove null check (if strictNullChecks enforced)
- if (price === undefined || price === null) return ""; + if (price === undefined) return "";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/lib/format.ts` around lines 17 - 21, The runtime checks in formatCredits reference null but the parameter type is number | bigint | undefined; update the declaration or the code to match: either add null to the type (change the parameter to price: number | bigint | undefined | null) and keep the existing if (price === undefined || price === null) return ""; or if your project enforces strictNullChecks and null should never be passed, remove the redundant null check so the function signature (formatCredits) and runtime behavior are consistent.
2-6: Asymmetric truncation: intentional?
truncateIdtakes head length fromn(default 8) but hardcodes tail to 6. If this is intentional (prioritizing prefix visibility), consider documenting it. If not, you may want to make the tail configurable or symmetric.// Current: "12345678…abcdef" (8 head, 6 tail) // Symmetric would be: "12345678…ghijklmn" (8 head, 8 tail)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/lib/format.ts` around lines 2 - 6, The truncateId function currently uses the head length parameter n but hardcodes the tail to 6, causing asymmetric truncation; update truncateId to either accept an explicit tail parameter (e.g., tail?: number) or make the tail symmetric by using n for both head and tail (replace slice(-6) with slice(-n)), and update the function signature and any callers accordingly, or if the asymmetry is intentional add a short doc comment on truncateId explaining why tail is fixed at 6.example-apps/dashmint-lab/src/components/NavButton.tsx (1)
14-21: Expose active nav state to assistive tech.Add
aria-currentwhenactiveis true so screen readers can announce the current section.♿ Proposed tweak
<button type="button" onClick={onClick} + aria-current={active ? 'page' : undefined} className={`relative flex w-full items-center gap-2 rounded-lg px-2.5 py-2 text-[13px] font-medium transition-[background,color] duration-[120ms] ${ active🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/components/NavButton.tsx` around lines 14 - 21, The NavButton component's button element doesn't expose the active state to assistive tech; update the button rendering (inside the NavButton component, where the <button ... onClick={onClick} className={...} is defined) to add aria-current="page" (or "true") only when the active prop is true so screen readers can announce the current section; ensure aria-current is omitted or set to undefined when active is false to avoid incorrect semantics.example-apps/dashmint-lab/test/PurchaseModal.test.tsx (1)
1-113: Move this test to a co-located path withPurchaseModal.The test is currently centralized under
test/; please place it next to the subject component to match the project’s test-location convention.As per coding guidelines: Test files should be co-located by subject and opt in to jsdom environment with //
@vitest-environmentjsdom when DOM is needed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/test/PurchaseModal.test.tsx` around lines 1 - 113, Move the test file from the centralized test/ directory to be co-located with the PurchaseModal component (same folder as PurchaseModal.tsx); keep the top line // `@vitest-environment` jsdom, update any relative import paths (e.g., imports of ../src/components/PurchaseModal, ../src/session/useSession, ../src/dash/purchaseCard, and ../src/dash/queries/types) to the new relative paths from the component folder, and ensure the test filename remains PurchaseModal.test.tsx so Vitest picks it up next to the component.example-apps/dashmint-lab/src/components/StatPair.tsx (1)
20-20: Clamp stat bar width at 0% minimum as well.Use a lower-bound clamp to prevent negative CSS widths if bad input slips through.
Proposed defensive tweak
- const pct = `${Math.min(100, (Math.min(10, value) / 10) * 100)}%`; + const clamped = Math.max(0, Math.min(10, value)); + const pct = `${(clamped / 10) * 100}%`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/components/StatPair.tsx` at line 20, The computed pct string in StatPair.tsx can become negative for bad input; update the pct calculation (the pct constant) to clamp the computed percentage between 0 and 100, e.g. wrap the inner computation with Math.max(0, ...) and then Math.min(100, ...), and keep the final value formatted as a percentage string so CSS widths never receive negative values.example-apps/dashmint-lab/test/CardTile.test.tsx (1)
1-204: Move this test next toCardTileto match the test-placement rule.The jsdom opt-in is correct, but this file should be co-located with its subject component to follow project conventions.
As per coding guidelines
example-apps/dashmint-lab/**/*.test.{ts,tsx}: Test files should be co-located by subject and opt in to jsdom environment with //@vitest-environmentjsdom when DOM is needed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/test/CardTile.test.tsx` around lines 1 - 204, The test file CardTile.test.tsx should be moved to the same directory as the CardTile component to satisfy the test-placement rule; relocate this file next to the component (the CardTile component in ../src/components/CardTile) and update any relative import paths if necessary, keeping the existing // `@vitest-environment` jsdom header and preserving references to useDpnsName, CardTile, and the test helpers so the tests continue to import CardTile, Card, and DashSdk correctly.example-apps/dashmint-lab/src/dash/transferCard.ts (1)
34-45: Keep this operation file wrapper-free persrc/dashrule.The mutation is routed through
withAuthedCard; this should be inlined so the SDK operation remains the direct exported function behavior.As per coding guidelines "
example-apps/dashmint-lab/src/dash/**/*.ts: Each Platform SDK operation file in src/dash/ should export an async function with a leading JSDoc block, with no hooks or wrappers — the SDK call is the function".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/dash/transferCard.ts` around lines 34 - 45, The export currently wraps the SDK call in withAuthedCard; remove that wrapper and inline the logic so the module exports a single async function that directly calls sdk.documents.transfer (preserving the parameters doc, recipientId, identityKey, signer) and retains the errorLabel/log behavior; specifically, replace the withAuthedCard invocation with an exported async function (keeping the JSDoc header requirement) that performs await sdk.documents.transfer({ document: doc, recipientId, identityKey, signer }) and then calls log?.("Card transferred!", "success").example-apps/dashmint-lab/src/data/starterPack.ts (1)
51-55: Return cloned card objects to avoid accidental shared-state mutation.
slice()copies the array, not the objects. A caller mutating a returned card can mutate future draws fromSTARTER_CARD_POOL.Proposed fix
export function drawStarterPack( random: () => number = Math.random, ): MintCardInput[] { - return shuffleCards(STARTER_CARD_POOL, random).slice(0, STARTER_PACK_SIZE); + return shuffleCards(STARTER_CARD_POOL, random) + .slice(0, STARTER_PACK_SIZE) + .map((card) => ({ ...card })); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/data/starterPack.ts` around lines 51 - 55, drawStarterPack currently returns references to objects from STARTER_CARD_POOL (slice only copies the array), allowing callers to mutate shared card objects; change drawStarterPack to return cloned card objects instead of originals—after calling shuffleCards(...).slice(0, STARTER_PACK_SIZE) map over the selected cards and deep-clone each item (e.g., structuredClone(item) or a project-standard clone utility) so the returned MintCardInput[] contains independent objects and avoids shared-state mutation.example-apps/dashmint-lab/test/SessionContext.test.tsx (1)
280-284: UsewaitForfor post-logout state assertions to reduce future flakiness.This assertion is currently immediate after click. Wrapping status/identity checks in
waitForwill make the test resilient if logout transitions become async.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/test/SessionContext.test.tsx` around lines 280 - 284, The post-logout assertions run immediately and can flake if logout is async; make the test resilient by importing waitFor from `@testing-library/react`, mark the test async if not already, and replace the immediate expects after fireEvent.click(screen.getByRole("button", { name: "Logout" })) with await waitFor(() => { expect(screen.getByTestId("status").textContent).toBe("browsing"); expect(screen.getByTestId("identity").textContent).toBe(""); }); so the checks for getByTestId("status") and getByTestId("identity") wait for the logout transition to complete.example-apps/dashmint-lab/src/dash/burnCard.ts (1)
31-54: Inline the SDK operation in this file instead of routing throughwithAuthedCard.This operation file currently delegates through a wrapper, which diverges from the
src/dashoperation-file rule.As per coding guidelines "
example-apps/dashmint-lab/src/dash/**/*.ts: Each Platform SDK operation file in src/dash/ should export an async function with a leading JSDoc block, with no hooks or wrappers — the SDK call is the function".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/dash/burnCard.ts` around lines 31 - 54, Replace the withAuthedCard wrapper call by exporting a standalone async function (e.g., export async function burnCard(...)) with a leading JSDoc block that takes the needed inputs and performs the SDK call directly: call sdk.documents.delete({ document: { id: cardId, ownerId: identity.id, dataContractId: contractId, documentTypeName: "card" }, identityKey, signer }) and then log success; remove any use of withAuthedCard and its wrapper-specific params (preFetch, errorLabel) and ensure the function signature exposes the unique symbols used (sdk, contractId, cardId, identity, identityKey, signer, log) so the SDK operation is inlined per the src/dash operation-file rule.example-apps/dashmint-lab/test/TransferModal.test.tsx (1)
1-12: Move this suite next toTransferModal.tsx.The
jsdomopt-in is correct, but this test lives undertest/instead of being co-located with its subject, which is the repo convention fordashmint-lab.As per coding guidelines, "
example-apps/dashmint-lab/**/*.test.{ts,tsx}: Test files should be co-located by subject and opt in to jsdom environment with //@vitest-environmentjsdom when DOM is needed".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/test/TransferModal.test.tsx` around lines 1 - 12, Move the test file so it is co-located with its subject: place TransferModal.test.tsx next to TransferModal.tsx (same directory as the TransferModal component), keeping the existing jsdom opt-in comment (// `@vitest-environment` jsdom) and preserving imports that reference the component (the TransferModal import) and test utilities (render, fireEvent, screen, etc.); after moving, update any relative import paths if needed to ensure the test still imports TransferModal from "./TransferModal" (or the correct relative path).example-apps/dashmint-lab/test/App.test.tsx (1)
1-585: Place this test next toApp.tsxto match co-location policy.
example-apps/dashmint-lab/test/App.test.tsxis not co-located with its subject (src/App.tsx). Prefersrc/App.test.tsx(or equivalent adjacent location).As per coding guidelines,
example-apps/dashmint-lab/**/*.test.{ts,tsx}: Test files should be co-located by subject and opt in to jsdom environment with //@vitest-environmentjsdom when DOM is needed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/test/App.test.tsx` around lines 1 - 585, The test file is not co-located with its subject; move example-apps/dashmint-lab/test/App.test.tsx to sit alongside the component under test (rename/move to example-apps/dashmint-lab/src/App.test.tsx or src/App.test.tsx next to App.tsx) and ensure the file still contains the jsdom opt-in header ("// `@vitest-environment` jsdom"); update any import paths if necessary so references to App and mocked modules (e.g., App.tsx, useSession, dash/queries, and component mocks like LoginModal/TransferModal) resolve from the new location.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@example-apps/dashmint-lab/.prettierrc.json`:
- Line 1: The Prettier config currently empty should be updated to enforce
repository formatting rules: set singleQuote to true, tabWidth to 2 (2-space
indentation), and trailingComma to "all" (or "es5"/"all" per project policy) so
all JS/TS/JSON/MD files follow the required style; edit the .prettierrc.json in
the repo to include these keys (singleQuote, tabWidth, trailingComma) so
Prettier applies the expected formatting across the repository.
In `@example-apps/dashmint-lab/eslint.config.js`:
- Around line 12-17: Update the ESLint flat config's extends chain to include
Airbnb's base rules by installing the peer packages (eslint-config-airbnb-base
and eslint-plugin-import) and using FlatCompat to wrap the legacy "airbnb-base"
config; specifically, import FlatCompat, create a compat instance (e.g., const
compat = new FlatCompat({/*...*/})), merge compat.extend('airbnb-base') into the
existing extends flow that currently references js.configs.recommended,
tseslint.configs.recommended, reactHooks.configs.flat.recommended, and
reactRefresh.configs.vite so the Airbnb rules are applied alongside those
configs.
In `@example-apps/dashmint-lab/src/App.tsx`:
- Around line 246-286: Currently MintForm is rendered whenever contractId exists
which allows authenticated users to interact during owner lookup; update the
gating so the form is only mounted when the caller is the confirmed owner by
requiring identityId === contractOwnerId (and status === "authenticated") before
rendering MintForm, treat contractOwnerId being null/undefined as a
blocked/loading state (show the non-owner/loading overlay when status ===
"authenticated" && !contractOwnerId), and keep the existing non-owner overlay
condition for status === "authenticated" && contractOwnerId && identityId !==
contractOwnerId so only the confirmed owner sees MintForm (MintForm,
contractOwnerId, identityId, status, contractId, setLoginOpen, refresh).
In `@example-apps/dashmint-lab/src/components/LoginModal.tsx`:
- Around line 17-23: LoginModal currently stores the sensitive mnemonic in React
state (mnemonic / setMnemonic); remove that state and convert the mnemonic input
to an uncontrolled field accessed via a ref or read from FormData in the submit
handler (e.g., use mnemonicRef or event.currentTarget) instead of setMnemonic,
and then read the value only at submit time; ensure you clear the DOM input
value (ref.current.value = '' or form.reset()) in onClose, on successful login,
and in error paths where setSubmitting/setError are used so the secret never
persists in state or the DOM after cancel/failure; update any handlers that
referenced mnemonic/state (submit handler, validation, and any setMnemonic
calls) to use the ref/FormData approach—apply the same change to the other
mnemonic/password inputs in this file.
In `@example-apps/dashmint-lab/src/components/MintForm.tsx`:
- Around line 24-27: Both handlers (handleSubmit and handleStarterPack) allow
re-entry and only toggle their own button state, permitting overlapping
documents.create calls; add a shared guard using the existing submitting state
to early-return if submitting is true at the top of each handler, set
submitting=true immediately when starting either handler, and reset
submitting=false in every exit path (including catch/finally) so no second mint
can start while one is in flight; update references in handleSubmit and
handleStarterPack and ensure any calls to documents.create happen only when the
guard passes.
In `@example-apps/dashmint-lab/src/components/Modal.tsx`:
- Around line 29-52: The modal lacks dialog semantics and focus handling: update
the Modal component to add role="dialog" and aria-modal="true" on the inner
container, bind aria-labelledby to the title element by generating a stable id
for the title (useId or a prop) and set that id on the h2, and add focus
management by giving the dialog container a ref (e.g., dialogRef) and focusing
it on mount (and handling Escape via onKeyDown to call onClose); ensure the
outer backdrop still handles click-to-close while the inner container keeps
e.stopPropagation().
In `@example-apps/dashmint-lab/src/components/PurchaseModal.tsx`:
- Around line 27-29: PurchaseModal currently keeps previous result/submitting
state because it stays mounted; add a useEffect in PurchaseModal that watches
the modal-opening signal (the card prop becoming non-null) and resets state by
calling setResult(null) and setSubmitting(false) whenever card transitions to a
non-null/open state so each reopen starts fresh; locate the PurchaseModal
component and update around the existing useSession/useState declarations
(reference result, setResult, submitting, setSubmitting) to add this reset
effect.
In `@example-apps/dashmint-lab/src/dash/classifyRecipientInput.ts`:
- Around line 18-20: In classifyRecipientInput, validate the character set
before treating dotted inputs as names: first check NON_RECIPIENT_CHAR against
trimmed and return "invalid" if it matches, then check NAME_ONLY_CHAR and return
"name" when it matches, and only after those checks use trimmed.includes(".") to
classify as "name"; this prevents malformed recipients (with invalid chars) from
being accepted for DPNS resolution and keeps the character-set-based distinction
between identity IDs and names intact.
In `@example-apps/dashmint-lab/src/dash/contract.ts`:
- Around line 133-143: The code currently falls back to the sentinel "unknown"
when sdk.contracts.publish(...) returns no id; instead, check published.id and
published.toJSON()?.id and if neither exists throw an explicit Error (including
any available debug info from published) rather than calling saveContractId or
returning a sentinel; update the function around the variables published,
contractId, saveContractId, and the return so that you only persist and log when
a valid contractId exists and otherwise throw.
In `@example-apps/dashmint-lab/src/dash/queries.ts`:
- Around line 22-23: The helpers that accept a caller-supplied limit forward it
unchanged and can exceed the platform cap defined by MAX_QUERY_LIMIT (const
MAX_QUERY_LIMIT = 100); update each helper that takes the parameter named limit
to clamp it with Math.min(limit ?? DEFAULT, MAX_QUERY_LIMIT) (or similar) before
constructing the query/SDK payload so the value sent is at most MAX_QUERY_LIMIT;
make this change in the three helper functions that receive a limit parameter
(the ones that build the query request body) so they always enforce the
MAX_QUERY_LIMIT constant.
In `@example-apps/dashmint-lab/src/dash/setPrice.ts`:
- Around line 32-33: Validate the incoming price before converting to BigInt: if
price is a number, ensure Number.isInteger(price) and price >= 0 and throw a
clear Error if not; if price is a bigint ensure price >= 0n and throw if
negative; only then compute priceBig (using BigInt for numbers after validation)
and compute removing as priceBig === 0n. Update references to price, priceBig,
and removing in setPrice to use the validated value and provide explicit error
messages for non-integer or negative inputs.
In `@example-apps/dashmint-lab/src/hooks/useDpnsName.ts`:
- Around line 37-73: The deduplication race comes from reading cached outside
the effect; change useEffect to read the cache inside (call
cache.get(identityId) within the effect) so concurrent hook instances in the
same render see the same value, and remove the outer cached from the dependency
list; then keep the current logic using the locally read cached (checking
instanceof Promise to reuse in-flight promise or start resolve(sdk,
identityId)), update cache.set when resolving, and preserve the cancelled
cleanup handling and forceRender usage (symbols: cached, useEffect, cache.get,
cache.set, resolve, identityId, sdk, forceRender).
In `@example-apps/dashmint-lab/src/hooks/useResolvedRecipient.ts`:
- Around line 22-27: The lookup function currently swallows all resolveDpnsName
errors and returns null which the hook then caches as “not-found”; change
lookup(sdk: DashSdk, fullName: string) to distinguish real “not found” results
from transient errors by rethrowing or returning a distinct error/result type on
exceptions (do not return null on catch), only treat/return null when
resolveDpnsName explicitly indicates no record, and update the cache-population
logic in the hook to only write successful resolutions or explicit not-found
markers (never cache the catch-path) — apply the same fix to the other
lookup/cache usage in this hook.
In `@example-apps/dashmint-lab/test/App.test.tsx`:
- Around line 214-227: Move the test file into the co-located path and reformat
it: relocate test/App.test.tsx to src/App.test.tsx (so it matches
example-apps/dashmint-lab/**/*.test.{ts,tsx}), then run Prettier with the
project style settings (single quotes, 2-space indent, trailing commas) over the
file; no code changes to the makeSession helper or its signature (function
makeSession) are required—just update the file location and apply the formatting
rules.
In `@example-apps/dashmint-lab/tsconfig.node.json`:
- Around line 6-15: The tsconfig.node.json uses module: "esnext" and
moduleResolution: "bundler" but must match the repository Node16 + strict
baseline; update the "module" field to "Node16", update "moduleResolution" to
"Node16", add "strict": true to the root of this config, and keep "types":
["node"] and "noEmit": true; also remove or disable bundler-specific flags
(e.g., "allowImportingTsExtensions" and "verbatimModuleSyntax") if they conflict
with Node16 resolution to ensure the compiler uses the Node16 module semantics.
In `@tsconfig.json`:
- Line 75: The root tsconfig currently excludes "example-apps" via the "exclude"
array, which prevents type-checking of that subtree; either remove
"example-apps" from the "exclude" list in tsconfig.json to re-include it in root
type-checking, or update each app's tsconfig.app.json inside that subtree (set
"compilerOptions": "strict": true, "noUnusedLocals": true, "module": "Node16",
"moduleResolution": "Node16") and add a CI job that runs a TypeScript
build/type-check (e.g., run tsc --noEmit or tsc --build) for those projects so
they are enforced in CI.
---
Minor comments:
In `@example-apps/dashmint-lab/CLAUDE.md`:
- Around line 16-18: The doc line claiming "no wrappers" for files under
src/dash/ conflicts with later requirements for using the withAuthedCard wrapper
for certain operations; update the README text to clarify that most SDK
operation files are plain functions (see classifyRecipientInput and
resolveRecipient helpers) but that operations which require authenticated card
context (e.g., transfer, set-price, purchase) must export or call through the
withAuthedCard wrapper; explicitly list/point to those functions and the
withAuthedCard usage as exceptions so contributors know when to apply the auth
wrapper.
In `@example-apps/dashmint-lab/package.json`:
- Line 17: The package.json currently lists "@tailwindcss/vite" as a regular
dependency but it is only used at build time in vite.config.ts; move the entry
for "@tailwindcss/vite" from "dependencies" to "devDependencies" in package.json
so it is not installed at runtime, and ensure any import or usage in
vite.config.ts remains unchanged (e.g., references to the plugin name in the
Vite config).
In `@example-apps/dashmint-lab/src/components/IdentityCard.tsx`:
- Around line 69-108: The button in IdentityCard.tsx uses onLoginClick but when
the user is in browse-only mode (status === 'browsing' / equivalently !isAuthed)
its visible label is only "Connected", which is misleading; update the <button>
element (and/or the “Connected” span) to provide an explicit action label by
adding an aria-label like "Open sign-in or settings" when !isAuthed (or status
=== 'browsing'), and/or render visible sign-in text (e.g., "Sign in" or "Open
account") next to the Connected label conditionally based on isAuthed, ensuring
accessibility attributes and text use the existing onLoginClick handler and do
not change the click behavior.
In `@example-apps/dashmint-lab/src/components/Modal.tsx`:
- Around line 41-47: The close button in Modal.tsx currently lacks an explicit
type which can cause it to submit a surrounding form; update the close button
element (the button with onClick={onClose} and aria-label="Close") to include
type="button" so it behaves as a non-submitting control and preserves the
onClose handler behavior.
In `@example-apps/dashmint-lab/src/dash/logger.ts`:
- Around line 8-11: The doc comment incorrectly says "four levels" while the
exported type LogLevel only defines three values ("info" | "success" | "error");
update the comment to reflect "three levels" (or otherwise reword/remove the
numeric claim) so the comment matches the LogLevel declaration in logger.ts.
In `@example-apps/dashmint-lab/src/dash/mintCard.ts`:
- Around line 45-46: Validate any provided attack/defense overrides in the mint
flow: in the code that sets attack and defense (the lines using rollStat()),
check card.attack and card.defense and only accept them if they are integers
between 1 and 10 (use Number.isInteger or equivalent); otherwise ignore the
override and call rollStat() to generate the value. Update mintCard.ts (the
attack/defense assignment logic) to perform this validation so non-integers or
out-of-range values (e.g., 0, 11, floats) cannot be persisted or used, and leave
rarity derivation to the client-side logic in src/lib/rarity.ts (do not persist
rarity). Ensure references to rollStat(), attack, and defense are the places you
change.
In `@example-apps/dashmint-lab/src/dash/resolveRecipient.ts`:
- Around line 24-27: The normalizeDpnsName function currently converts
whitespace-only input into ".dash"; update normalizeDpnsName to trim and
lower-case the input, then check that the resulting name is non-empty and not
just dots before appending ".dash" — if it is empty after normalization throw a
clear, user-facing error (e.g., "Recipient name cannot be empty") so callers get
a proper validation failure instead of a misleading lookup path.
In `@example-apps/dashmint-lab/src/dash/transferCard.ts`:
- Around line 31-40: Normalize and validate recipientId by trimming whitespace
before the truthiness check and usage: replace the current guard with const
normalizedRecipientId = recipientId?.trim(); if (!normalizedRecipientId) throw
new Error("Recipient identity ID is required."); then use normalizedRecipientId
in the log call and pass normalizedRecipientId to sdk.documents.transfer inside
the withAuthedCard callback (referencing recipientId, sdk.documents.transfer,
and withAuthedCard).
In `@example-apps/dashmint-lab/src/lib/explorer.ts`:
- Line 1: The EXPLORER_BASE constant in explorer.ts uses double quotes; update
the declaration of EXPLORER_BASE to use single quotes to comply with the
repository Prettier rules (single quotes, 2-space indentation, trailing commas)
so change the string delimiter for the constant EXPLORER_BASE from
"https://testnet.platform-explorer.com" to use single quotes.
In `@example-apps/dashmint-lab/src/session/SessionContext.tsx`:
- Around line 161-223: After successful state transitions clear any previous
error by calling setError(null): in the login function (inside the try block
after identity is set and before/when setStatus("authenticated") is called) add
setError(null); in browseOnly (inside the try block after successful connection
and before setStatus("browsing")) add setError(null); and in logout add
setError(null) alongside the existing state clears. Reference
functions/identifiers: login, browseOnly, logout, setError, setStatus,
setKeyManager, setIdentityId, setBalance.
- Around line 171-184: The login function currently passes the public
identityIndex straight through to IdentityKeyManager.create; sanitize it first
by coercing to an integer and clamping to a non-negative value (e.g., compute a
safeIndex = Math.max(0, Math.floor(Number(identityIndex || 0)))) and use
safeIndex in the call to IdentityKeyManager.create, and optionally throw a clear
error if identityIndex is NaN/invalid before attempting creation.
In `@example-apps/dashmint-lab/test/App.test.tsx`:
- Around line 3-584: The file fails repo Prettier rules (uses double quotes and
inconsistent trailing commas/indentation); run Prettier with the repo config to
convert to single quotes, 2-space indentation and add trailing commas. Reformat
the test file (symbols to locate: makeSession, cards, all vi.mock blocks and
describe("App") tests) or run the project-wide Prettier command (e.g., npm/yarn
prettier --write) so the import strings, JSX attributes, and object literals use
single quotes and conform to trailing-comma and 2-space rules before merging.
In `@example-apps/dashmint-lab/test/resolveRecipient.test.ts`:
- Around line 9-15: The test never exercises the non-string error branch because
sdkWith's dpns.resolveName mock returns null instead of a non-string value;
update the failing test to have sdkWith(...) supply a resolve implementation
that returns undefined (or another non-string) for the non-string path so the
guard in the code is exercised, e.g., change the resolveName mock used in the
"non-string" case to return Promise.resolve(undefined) and assert the expected
error/handling; look for the sdkWith helper and tests that call it and modify
the resolve implementation for the non-string scenario.
---
Nitpick comments:
In `@example-apps/dashmint-lab/src/components/NavButton.tsx`:
- Around line 14-21: The NavButton component's button element doesn't expose the
active state to assistive tech; update the button rendering (inside the
NavButton component, where the <button ... onClick={onClick} className={...} is
defined) to add aria-current="page" (or "true") only when the active prop is
true so screen readers can announce the current section; ensure aria-current is
omitted or set to undefined when active is false to avoid incorrect semantics.
In `@example-apps/dashmint-lab/src/components/StatPair.tsx`:
- Line 20: The computed pct string in StatPair.tsx can become negative for bad
input; update the pct calculation (the pct constant) to clamp the computed
percentage between 0 and 100, e.g. wrap the inner computation with Math.max(0,
...) and then Math.min(100, ...), and keep the final value formatted as a
percentage string so CSS widths never receive negative values.
In `@example-apps/dashmint-lab/src/dash/burnCard.ts`:
- Around line 31-54: Replace the withAuthedCard wrapper call by exporting a
standalone async function (e.g., export async function burnCard(...)) with a
leading JSDoc block that takes the needed inputs and performs the SDK call
directly: call sdk.documents.delete({ document: { id: cardId, ownerId:
identity.id, dataContractId: contractId, documentTypeName: "card" },
identityKey, signer }) and then log success; remove any use of withAuthedCard
and its wrapper-specific params (preFetch, errorLabel) and ensure the function
signature exposes the unique symbols used (sdk, contractId, cardId, identity,
identityKey, signer, log) so the SDK operation is inlined per the src/dash
operation-file rule.
In `@example-apps/dashmint-lab/src/dash/transferCard.ts`:
- Around line 34-45: The export currently wraps the SDK call in withAuthedCard;
remove that wrapper and inline the logic so the module exports a single async
function that directly calls sdk.documents.transfer (preserving the parameters
doc, recipientId, identityKey, signer) and retains the errorLabel/log behavior;
specifically, replace the withAuthedCard invocation with an exported async
function (keeping the JSDoc header requirement) that performs await
sdk.documents.transfer({ document: doc, recipientId, identityKey, signer }) and
then calls log?.("Card transferred!", "success").
In `@example-apps/dashmint-lab/src/data/starterPack.ts`:
- Around line 51-55: drawStarterPack currently returns references to objects
from STARTER_CARD_POOL (slice only copies the array), allowing callers to mutate
shared card objects; change drawStarterPack to return cloned card objects
instead of originals—after calling shuffleCards(...).slice(0, STARTER_PACK_SIZE)
map over the selected cards and deep-clone each item (e.g.,
structuredClone(item) or a project-standard clone utility) so the returned
MintCardInput[] contains independent objects and avoids shared-state mutation.
In `@example-apps/dashmint-lab/src/lib/explorer.ts`:
- Around line 3-9: The identityUrl and documentUrl helpers currently interpolate
raw IDs into paths (identityUrl and documentUrl using EXPLORER_BASE), which can
produce broken/unsafe links; update both functions to encode the path parameters
with encodeURIComponent (or a project-wide safe encoder) before concatenating
with EXPLORER_BASE so malformed or unsafe identityId/documentId values produce
valid URLs.
In `@example-apps/dashmint-lab/src/lib/format.ts`:
- Around line 17-21: The runtime checks in formatCredits reference null but the
parameter type is number | bigint | undefined; update the declaration or the
code to match: either add null to the type (change the parameter to price:
number | bigint | undefined | null) and keep the existing if (price ===
undefined || price === null) return ""; or if your project enforces
strictNullChecks and null should never be passed, remove the redundant null
check so the function signature (formatCredits) and runtime behavior are
consistent.
- Around line 2-6: The truncateId function currently uses the head length
parameter n but hardcodes the tail to 6, causing asymmetric truncation; update
truncateId to either accept an explicit tail parameter (e.g., tail?: number) or
make the tail symmetric by using n for both head and tail (replace slice(-6)
with slice(-n)), and update the function signature and any callers accordingly,
or if the asymmetry is intentional add a short doc comment on truncateId
explaining why tail is fixed at 6.
In `@example-apps/dashmint-lab/src/styles/globals.css`:
- Line 1: The `@import` at-rule currently uses url(...) notation which Stylelint's
import-notation rule flags; replace the url(...) form with string notation by
changing `@import` url("https://fonts.googleapis.com/...") to `@import`
"https://fonts.googleapis.com/..."; update the `@import` statement that references
the Inter and JetBrains Mono fonts so it uses a quoted string instead of url(),
preserving the same URL and quotes.
In `@example-apps/dashmint-lab/test/App.test.tsx`:
- Around line 1-585: The test file is not co-located with its subject; move
example-apps/dashmint-lab/test/App.test.tsx to sit alongside the component under
test (rename/move to example-apps/dashmint-lab/src/App.test.tsx or
src/App.test.tsx next to App.tsx) and ensure the file still contains the jsdom
opt-in header ("// `@vitest-environment` jsdom"); update any import paths if
necessary so references to App and mocked modules (e.g., App.tsx, useSession,
dash/queries, and component mocks like LoginModal/TransferModal) resolve from
the new location.
In `@example-apps/dashmint-lab/test/CardTile.test.tsx`:
- Around line 1-204: The test file CardTile.test.tsx should be moved to the same
directory as the CardTile component to satisfy the test-placement rule; relocate
this file next to the component (the CardTile component in
../src/components/CardTile) and update any relative import paths if necessary,
keeping the existing // `@vitest-environment` jsdom header and preserving
references to useDpnsName, CardTile, and the test helpers so the tests continue
to import CardTile, Card, and DashSdk correctly.
In `@example-apps/dashmint-lab/test/PurchaseModal.test.tsx`:
- Around line 1-113: Move the test file from the centralized test/ directory to
be co-located with the PurchaseModal component (same folder as
PurchaseModal.tsx); keep the top line // `@vitest-environment` jsdom, update any
relative import paths (e.g., imports of ../src/components/PurchaseModal,
../src/session/useSession, ../src/dash/purchaseCard, and
../src/dash/queries/types) to the new relative paths from the component folder,
and ensure the test filename remains PurchaseModal.test.tsx so Vitest picks it
up next to the component.
In `@example-apps/dashmint-lab/test/SessionContext.test.tsx`:
- Around line 280-284: The post-logout assertions run immediately and can flake
if logout is async; make the test resilient by importing waitFor from
`@testing-library/react`, mark the test async if not already, and replace the
immediate expects after fireEvent.click(screen.getByRole("button", { name:
"Logout" })) with await waitFor(() => {
expect(screen.getByTestId("status").textContent).toBe("browsing");
expect(screen.getByTestId("identity").textContent).toBe(""); }); so the checks
for getByTestId("status") and getByTestId("identity") wait for the logout
transition to complete.
In `@example-apps/dashmint-lab/test/TransferModal.test.tsx`:
- Around line 1-12: Move the test file so it is co-located with its subject:
place TransferModal.test.tsx next to TransferModal.tsx (same directory as the
TransferModal component), keeping the existing jsdom opt-in comment (//
`@vitest-environment` jsdom) and preserving imports that reference the component
(the TransferModal import) and test utilities (render, fireEvent, screen, etc.);
after moving, update any relative import paths if needed to ensure the test
still imports TransferModal from "./TransferModal" (or the correct relative
path).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
…ntity Both single-mint and starter-pack handlers tracked their own in-flight flag, letting a user trigger overlapping documents.create calls that race for the identity nonce. Each handler now early-returns if either flag is set, and each button disables while either operation is in flight. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ement Add role="dialog", aria-modal, and aria-labelledby tying the title to the container so screen readers announce the modal correctly. Focus the dialog on open so keyboard users land inside the modal instead of tabbing through the page behind it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PurchaseModal stays mounted across opens, so a prior error notice would re-appear next time the user opened the modal. Add the same card-watch reset effect TransferModal already uses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
classifyRecipientInput short-circuited to "name" on any dotted string, bypassing the character-set check, so inputs like "alice@dash.foo" were forwarded to DPNS resolution. Run the disallowed-char check first. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously fell back to the literal string "unknown", which would be persisted to localStorage and returned as if it were a real contract ID, poisoning every downstream query. Throw with the published payload for debug context instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
example-apps/dashmint-lab/test/classifyRecipientInput.test.ts (1)
1-51: Consider co-locating this test withclassifyRecipientInputsource.The assertions look good, but placing this test next to
example-apps/dashmint-lab/src/dash/classifyRecipientInput.tswould align better with the repo’s test-location rule and make maintenance/navigation easier.As per coding guidelines
example-apps/dashmint-lab/test/**/*.test.{ts,tsx}: “Test files should be co-located with their subjects using Vitest + Testing Library.”🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/test/classifyRecipientInput.test.ts` around lines 1 - 51, The test file should be co-located with its subject: move example-apps/dashmint-lab/test/classifyRecipientInput.test.ts next to the implementation in example-apps/dashmint-lab/src/dash/, update the import to reference "./classifyRecipientInput" (the classifyRecipientInput function), and delete the old test file; ensure the new path matches the repo's test glob (test/**/*.test.{ts,tsx} or adjust vitest config if needed) so Vitest picks up classifyRecipientInput.test.ts in src/dash.example-apps/dashmint-lab/src/components/MintForm.tsx (1)
27-27: Optional cleanup: centralize the busy predicate to avoid drift.
submitting || mintingPackis repeated in multiple guards/UI bindings. Extracting oneisBusyconstant will keep handler and button logic in sync.♻️ Suggested refactor
const [submitting, setSubmitting] = useState(false); const [mintingPack, setMintingPack] = useState(false); + const isBusy = submitting || mintingPack; @@ async function handleSubmit(e: FormEvent) { e.preventDefault(); if (!session.sdk || !session.keyManager) return; - if (submitting || mintingPack) return; + if (isBusy) return; @@ async function handleStarterPack() { if (!session.sdk || !session.keyManager) return; - if (submitting || mintingPack) return; + if (isBusy) return; @@ <button type="submit" - disabled={submitting || mintingPack || !name.trim()} + disabled={isBusy || !name.trim()} @@ <button type="button" onClick={handleStarterPack} - disabled={submitting || mintingPack} + disabled={isBusy}Also applies to: 49-49, 126-126, 147-147
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@example-apps/dashmint-lab/src/components/MintForm.tsx` at line 27, In MintForm.tsx, centralize the repeated busy predicate by adding a single const isBusy = submitting || mintingPack inside the MintForm component and replace all direct uses of `submitting || mintingPack` (guards in handlers and button disabled/conditional rendering) with `isBusy` so the handler checks (e.g., the early return `if (submitting || mintingPack) return;`) and all button/UX bindings stay in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@example-apps/dashmint-lab/src/components/MintForm.tsx`:
- Line 27: In MintForm.tsx, centralize the repeated busy predicate by adding a
single const isBusy = submitting || mintingPack inside the MintForm component
and replace all direct uses of `submitting || mintingPack` (guards in handlers
and button disabled/conditional rendering) with `isBusy` so the handler checks
(e.g., the early return `if (submitting || mintingPack) return;`) and all
button/UX bindings stay in sync.
In `@example-apps/dashmint-lab/test/classifyRecipientInput.test.ts`:
- Around line 1-51: The test file should be co-located with its subject: move
example-apps/dashmint-lab/test/classifyRecipientInput.test.ts next to the
implementation in example-apps/dashmint-lab/src/dash/, update the import to
reference "./classifyRecipientInput" (the classifyRecipientInput function), and
delete the old test file; ensure the new path matches the repo's test glob
(test/**/*.test.{ts,tsx} or adjust vitest config if needed) so Vitest picks up
classifyRecipientInput.test.ts in src/dash.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b8fd8d4b-e5ae-4880-95cc-7017e2c98a35
📒 Files selected for processing (5)
example-apps/dashmint-lab/src/components/MintForm.tsxexample-apps/dashmint-lab/src/components/Modal.tsxexample-apps/dashmint-lab/src/components/PurchaseModal.tsxexample-apps/dashmint-lab/src/dash/classifyRecipientInput.tsexample-apps/dashmint-lab/test/classifyRecipientInput.test.ts
✅ Files skipped from review due to trivial changes (1)
- example-apps/dashmint-lab/src/dash/classifyRecipientInput.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- example-apps/dashmint-lab/src/components/Modal.tsx
- example-apps/dashmint-lab/src/components/PurchaseModal.tsx
Both useDpnsName and useResolvedRecipient closed over a cache value read during render, so two hook instances mounting in the same render with the same key both saw undefined and started independent lookups. Read the cache inside the effect instead so the second instance picks up the first one's in-flight promise. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
resolveDpnsName threw for both "name has no record" and SDK/network errors, and useResolvedRecipient swallowed both into null — caching a transient failure as a permanent "not-found" for the rest of the session. Make resolveDpnsName return null only for confirmed not-found and propagate real errors. The hook now evicts the in-flight cache entry on rejection so the next render retries. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Recent automated review findings tripped on documentation ambiguity: they applied root Node16/strict conventions to the nested Vite app and misread "co-located by subject" as "next to the source file." Make the root CLAUDE.md spell out that example-apps/ has its own toolchain, and restate what the root tsconfig actually covers. In dashmint-lab, point the test-layout bullet at the vitest include pattern explicitly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@example-apps/dashmint-lab/src/hooks/useResolvedRecipient.ts`:
- Around line 29-33: Trim the incoming input before using it for classification
and normalization: call input.trim() (or assign a trimmed variable) when
computing mode (replace classifyRecipientInput(input) with
classifyRecipientInput(trimmed)) and when computing key (replace
normalizeDpnsName(input!) with normalizeDpnsName(trimmed)), and ensure you treat
an all-whitespace trimmed string as empty so shouldResolve uses the trimmed
value; update references in useResolvedRecipient (symbols:
classifyRecipientInput, normalizeDpnsName, mode, shouldResolve, key, cache.get)
accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b818915e-8f87-4887-b504-29d279ff540e
📒 Files selected for processing (7)
CLAUDE.mdexample-apps/dashmint-lab/CLAUDE.mdexample-apps/dashmint-lab/src/dash/contract.tsexample-apps/dashmint-lab/src/dash/resolveRecipient.tsexample-apps/dashmint-lab/src/hooks/useDpnsName.tsexample-apps/dashmint-lab/src/hooks/useResolvedRecipient.tsexample-apps/dashmint-lab/test/resolveRecipient.test.ts
✅ Files skipped from review due to trivial changes (1)
- CLAUDE.md
🚧 Files skipped from review as they are similar to previous changes (3)
- example-apps/dashmint-lab/test/resolveRecipient.test.ts
- example-apps/dashmint-lab/CLAUDE.md
- example-apps/dashmint-lab/src/dash/contract.ts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
example-apps/dashmint-lab/— a React + TypeScript + Vite app for minting, viewing, transferring, and trading NFT-style collectible cards on Dash Platform testnet.setupDashClient-core.mjs(no vendoring), aliases@dashevo/evo-sdkto the app's local copy, and integrates with host tooling (TypeScript, Prettier).VITE_BASE_PATH.Highlights
name.dashnames; resolution is cached per-render.CardArt.tsxrenders deterministic per-card art (presentation-only).dashproof-lab(Dash blue).Architecture
src/dash/— one file per Platform SDK operation (mint, transfer, setPrice, purchase, burn, query, DPNS).src/session/— context provides{ sdk, keyManager, identityId, contractId, contractOwnerId, balance, activity }. Mnemonic stays in the keyManager closure — never in state, never inlocalStorage.src/hooks/useDpnsName,useResolvedRecipient— module-level caches.src/lib/— pure utilities:rarity,format,explorer,cardArt.withAuthedCard.ts(fetch document, bump revision, resolve auth signer).Summary by CodeRabbit
New Features
Documentation
Tests
Chores