Compare commits

..

6 Commits

Author SHA1 Message Date
hsiegeln
83722aeb7c chore: bump v0.1.51 (v0.1.50 registry corruption)
All checks were successful
Build & Publish / publish (push) Successful in 1m46s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:50:23 +02:00
hsiegeln
2709d4c164 chore: rename cameleer3 assets to cameleer, bump v0.1.50
All checks were successful
Build & Publish / publish (push) Successful in 1m3s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:42:25 +02:00
hsiegeln
549553c05b chore: rename cameleer3 to cameleer
All checks were successful
Build & Publish / publish (push) Successful in 2m9s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:28:11 +02:00
hsiegeln
ac3b69f864 feat: add onNavigate callback to Breadcrumb for SPA routing
All checks were successful
Build & Publish / publish (push) Successful in 2m1s
Breadcrumb used plain <a href> which bypasses React Router's basename,
breaking navigation in multi-tenant setups with a base path prefix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 09:45:38 +02:00
hsiegeln
a62ff5b064 feat: add source badge to LogViewer entries
All checks were successful
Build & Publish / publish (push) Successful in 1m58s
SonarQube Analysis / sonarqube (push) Successful in 2m47s
LogEntry now accepts an optional `source` field. When present, a small
badge (container/app/agent) renders between the level and message columns.
Backward compatible — entries without source render as before.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 23:30:29 +02:00
hsiegeln
53a9ed015a fix: limit x-axis tick labels to prevent overlap
All checks were successful
Build & Publish / publish (push) Successful in 1m23s
SonarQube Analysis / sonarqube (push) Successful in 2m31s
Auto-calculates tick interval to show ~6 labels max, with minTickGap
as safety net. Fixes unreadable x-axis on short time ranges (1h).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 22:08:03 +02:00
19 changed files with 196 additions and 18 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
node_modules/
dist/
.claude/
.superpowers/
.worktrees/
test-results/

101
AGENTS.md Normal file
View File

@@ -0,0 +1,101 @@
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **design-system** (1479 symbols, 2371 relationships, 24 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
## Always Do
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.
## When Debugging
1. `gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
2. `gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
3. `READ gitnexus://repo/design-system/process/{processName}` — trace the full execution flow step by step
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed
## When Refactoring
- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`.
- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code.
- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed.
## Never Do
- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.
## Tools Quick Reference
| Tool | When to use | Command |
|------|-------------|---------|
| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` |
| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` |
| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` |
| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` |
| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` |
| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` |
## Impact Risk Levels
| Depth | Meaning | Action |
|-------|---------|--------|
| d=1 | WILL BREAK — direct callers/importers | MUST update these |
| d=2 | LIKELY AFFECTED — indirect deps | Should test |
| d=3 | MAY NEED TESTING — transitive | Test if critical path |
## Resources
| Resource | Use for |
|----------|---------|
| `gitnexus://repo/design-system/context` | Codebase overview, check index freshness |
| `gitnexus://repo/design-system/clusters` | All functional areas |
| `gitnexus://repo/design-system/processes` | All execution flows |
| `gitnexus://repo/design-system/process/{name}` | Step-by-step execution trace |
## Self-Check Before Finishing
Before completing any code modification task, verify:
1. `gitnexus_impact` was run for all modified symbols
2. No HIGH/CRITICAL risk warnings were ignored
3. `gitnexus_detect_changes()` confirms changes match expected scope
4. All d=1 (WILL BREAK) dependents were updated
## Keeping the Index Fresh
After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it:
```bash
npx gitnexus analyze
```
If the index previously included embeddings, preserve them by adding `--embeddings`:
```bash
npx gitnexus analyze --embeddings
```
To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.**
> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`.
## CLI
| Task | Read this skill file |
|------|---------------------|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
<!-- gitnexus:end -->

View File

@@ -132,7 +132,7 @@ import camelSvg from '@cameleer/design-system/assets/camel-logo.svg' // simp
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **design-system** (1461 symbols, 2336 relationships, 23 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
This project is indexed by GitNexus as **design-system** (1479 symbols, 2371 relationships, 24 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

View File

Before

Width:  |  Height:  |  Size: 941 B

After

Width:  |  Height:  |  Size: 941 B

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 151 KiB

View File

Before

Width:  |  Height:  |  Size: 342 KiB

After

Width:  |  Height:  |  Size: 342 KiB

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -1,6 +1,6 @@
{
"name": "@cameleer/design-system",
"version": "0.1.47",
"version": "0.1.51",
"type": "module",
"main": "./dist/index.es.js",
"module": "./dist/index.es.js",

View File

@@ -8,9 +8,10 @@ interface BreadcrumbItem {
interface BreadcrumbProps {
items: BreadcrumbItem[]
className?: string
onNavigate?: (href: string) => void
}
export function Breadcrumb({ items, className }: BreadcrumbProps) {
export function Breadcrumb({ items, className, onNavigate }: BreadcrumbProps) {
return (
<nav aria-label="Breadcrumb" className={className}>
<ol className={styles.list}>
@@ -22,7 +23,11 @@ export function Breadcrumb({ items, className }: BreadcrumbProps) {
{isLast ? (
<span className={styles.active}>{item.label}</span>
) : item.href ? (
<a href={item.href} className={styles.link}>
<a
href={item.href}
className={styles.link}
onClick={onNavigate ? (e) => { e.preventDefault(); onNavigate(item.href!) } : undefined}
>
{item.label}
</a>
) : (

View File

@@ -61,6 +61,38 @@
background: color-mix(in srgb, var(--text-faint) 8%, transparent);
}
.sourceBadge {
flex-shrink: 0;
font-size: 9px;
font-family: var(--font-mono);
padding: 1px 6px;
border-radius: 3px;
line-height: 1.5;
white-space: nowrap;
min-width: 48px;
text-align: center;
}
.sourceContainer {
color: var(--text-muted);
background: color-mix(in srgb, var(--text-muted) 10%, transparent);
}
.sourceApp {
color: var(--running);
background: color-mix(in srgb, var(--running) 10%, transparent);
}
.sourceAgent {
color: var(--warning);
background: color-mix(in srgb, var(--warning) 10%, transparent);
}
.sourceDefault {
color: var(--text-muted);
background: color-mix(in srgb, var(--text-muted) 8%, transparent);
}
.message {
font-size: 12px;
font-family: var(--font-mono);

View File

@@ -3,9 +3,9 @@ import { render, screen } from '@testing-library/react'
import { LogViewer, type LogEntry } from './LogViewer'
const entries: LogEntry[] = [
{ timestamp: '2024-01-15T10:30:00Z', level: 'info', message: 'Server started' },
{ timestamp: '2024-01-15T10:30:05Z', level: 'warn', message: 'High memory usage' },
{ timestamp: '2024-01-15T10:30:10Z', level: 'error', message: 'Connection failed' },
{ timestamp: '2024-01-15T10:30:00Z', level: 'info', message: 'Server started', source: 'app' },
{ timestamp: '2024-01-15T10:30:05Z', level: 'warn', message: 'High memory usage', source: 'container' },
{ timestamp: '2024-01-15T10:30:10Z', level: 'error', message: 'Connection failed', source: 'agent' },
{ timestamp: '2024-01-15T10:30:15Z', level: 'debug', message: 'Query executed in 3ms' },
{ timestamp: '2024-01-15T10:30:20Z', level: 'trace', message: 'Entering handleRequest()' },
]
@@ -52,6 +52,23 @@ describe('LogViewer', () => {
expect(el.classList.contains('custom-class')).toBe(true)
})
it('renders source badges when source is provided', () => {
render(<LogViewer entries={entries} />)
expect(screen.getByText('app')).toBeInTheDocument()
expect(screen.getByText('container')).toBeInTheDocument()
expect(screen.getByText('agent')).toBeInTheDocument()
})
it('omits source badge when source is not provided', () => {
const noSourceEntries: LogEntry[] = [
{ timestamp: '2024-01-15T10:30:00Z', level: 'info', message: 'No source here' },
]
render(<LogViewer entries={noSourceEntries} />)
expect(screen.getByText('No source here')).toBeInTheDocument()
expect(screen.queryByText('app')).not.toBeInTheDocument()
expect(screen.queryByText('container')).not.toBeInTheDocument()
})
it('has role="log" for accessibility', () => {
render(<LogViewer entries={entries} />)
expect(screen.getByRole('log')).toBeInTheDocument()

View File

@@ -5,6 +5,7 @@ export interface LogEntry {
timestamp: string
level: 'info' | 'warn' | 'error' | 'debug' | 'trace'
message: string
source?: string
}
export interface LogViewerProps {
@@ -21,6 +22,12 @@ const LEVEL_CLASS: Record<LogEntry['level'], string> = {
trace: styles.levelTrace,
}
const SOURCE_CLASS: Record<string, string> = {
container: styles.sourceContainer,
app: styles.sourceApp,
agent: styles.sourceAgent,
}
function formatTime(iso: string): string {
try {
return new Date(iso).toLocaleTimeString('en-GB', {
@@ -67,6 +74,11 @@ export function LogViewer({ entries, maxHeight = 400, className }: LogViewerProp
<span className={[styles.levelBadge, LEVEL_CLASS[entry.level]].join(' ')}>
{entry.level.toUpperCase()}
</span>
{entry.source && (
<span className={[styles.sourceBadge, SOURCE_CLASS[entry.source] ?? styles.sourceDefault].join(' ')}>
{entry.source}
</span>
)}
<span className={styles.message}>{entry.message}</span>
</div>
))}

View File

@@ -36,6 +36,12 @@ export function ThemedChart({
return null
}
// Show ~5-6 ticks max to avoid label overlap
const maxTicks = 6
const tickInterval = data.length > maxTicks
? Math.ceil(data.length / maxTicks) - 1
: 0
return (
<div className={className} style={{ width: '100%', height }}>
<ResponsiveContainer width="100%" height="100%">
@@ -46,6 +52,8 @@ export function ThemedChart({
type={xType}
{...rechartsTheme.xAxis}
tickFormatter={xTickFormatter}
interval={tickInterval}
minTickGap={40}
/>
<YAxis
{...rechartsTheme.yAxis}

View File

@@ -14,6 +14,7 @@ interface TopBarProps {
user?: { name: string }
userMenuItems?: import('../../composites/Dropdown/Dropdown').DropdownItem[]
onLogout?: () => void
onNavigate?: (href: string) => void
className?: string
children?: ReactNode
}
@@ -24,6 +25,7 @@ export function TopBar({
user,
userMenuItems,
onLogout,
onNavigate,
className,
children,
}: TopBarProps) {
@@ -33,7 +35,7 @@ export function TopBar({
return (
<header className={`${styles.topbar} ${className ?? ''}`}>
{/* Left: Breadcrumb */}
<Breadcrumb items={breadcrumbOverride ?? breadcrumb} className={styles.breadcrumb} />
<Breadcrumb items={breadcrumbOverride ?? breadcrumb} className={styles.breadcrumb} onNavigate={onNavigate} />
{/* Center: consumer-provided controls */}
{children}

View File

@@ -1,7 +1,7 @@
import styles from './BrandAssetsSection.module.css'
import camelLogoSvg from '../../../assets/camel-logo.svg'
import cameleer3Logo from '../../../assets/cameleer3-logo.png'
import cameleer3LogoSvg from '../../../assets/cameleer3-logo.svg'
import cameleerLogo from '../../../assets/cameleer-logo.png'
import cameleerLogoSvg from '../../../assets/cameleer-logo.svg'
const LOGO_SIZES = [16, 32, 48, 180, 192, 512] as const
@@ -11,7 +11,7 @@ export function BrandAssetsSection() {
<h2 className={styles.sectionTitle}>Brand Assets</h2>
<div className={styles.componentCard}>
<h3 className={styles.componentTitle}>Cameleer3 Logo (PNG)</h3>
<h3 className={styles.componentTitle}>Cameleer Logo (PNG)</h3>
<p className={styles.componentDesc}>
Full-resolution logo and pre-generated size variants for favicons, PWA icons, and social images.
Shipped as static assets via <code>@cameleer/design-system/assets/*</code> export.
@@ -22,27 +22,27 @@ export function BrandAssetsSection() {
<div key={size} className={styles.logoItem}>
<div className={styles.logoPreview}>
<img
src={cameleer3Logo}
src={cameleerLogo}
alt={`Logo ${size}×${size}`}
width={Math.min(size, 96)}
height={Math.min(size, 96)}
/>
</div>
<span className={styles.logoLabel}>{size}×{size}</span>
<code className={styles.logoExport}>assets/cameleer3-{size}.png</code>
<code className={styles.logoExport}>assets/cameleer-{size}.png</code>
</div>
))}
<div className={styles.logoItem}>
<div className={styles.logoPreview}>
<img
src={cameleer3Logo}
src={cameleerLogo}
alt="Full resolution logo"
width={96}
height={96}
/>
</div>
<span className={styles.logoLabel}>Original</span>
<code className={styles.logoExport}>assets/cameleer3-logo.png</code>
<code className={styles.logoExport}>assets/cameleer-logo.png</code>
</div>
</div>
</div>
@@ -57,10 +57,10 @@ export function BrandAssetsSection() {
<div className={styles.logoGrid}>
<div className={styles.logoItem}>
<div className={styles.logoPreview}>
<img src={cameleer3LogoSvg} alt="Cameleer3 SVG logo" width={96} height={96} />
<img src={cameleerLogoSvg} alt="Cameleer SVG logo" width={96} height={96} />
</div>
<span className={styles.logoLabel}>Cameleer3 SVG</span>
<code className={styles.logoExport}>assets/cameleer3-logo.svg</code>
<span className={styles.logoLabel}>Cameleer SVG</span>
<code className={styles.logoExport}>assets/cameleer-logo.svg</code>
</div>
<div className={styles.logoItem}>
<div className={styles.logoPreview}>