@propriety/court-calendar 1.0.127 → 1.0.129
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/library-api-reviewer.md +22 -0
- package/.claude/settings.json +19 -0
- package/.claude/skills/gen-test/SKILL.md +24 -0
- package/.claude/skills/release-prep/SKILL.md +18 -0
- package/CLAUDE.md +41 -0
- package/dist/index.mjs +3 -10
- package/package.json +1 -1
- package/src/_components/LoadingBar.tsx +48 -54
- package/src/helpers/cases.ts +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: library-api-reviewer
|
|
3
|
+
description: Reviews changes to @propriety/court-calendar for public API compatibility — breaking prop changes, removed exports, peer dep violations. Invoke before opening a PR or publishing.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are a library API compatibility reviewer for `@propriety/court-calendar`.
|
|
7
|
+
|
|
8
|
+
Analyze the current diff or staged changes and identify:
|
|
9
|
+
|
|
10
|
+
1. **Removed or renamed exports** — check `src/index.ts` for anything removed or renamed that consumers may depend on
|
|
11
|
+
2. **Breaking prop changes** — removed props, changed types (stricter or different), new required props added to existing components
|
|
12
|
+
3. **Peer dependency violations** — code that directly imports from `@mui/`, `@emotion/`, `react`, `react-dom` without accounting for them being peer deps (they must not be bundled)
|
|
13
|
+
4. **Type regressions** — public TypeScript types that became more restrictive or were removed
|
|
14
|
+
5. **Dexie schema changes** — any changes to IndexedDB stores in `src/` must handle version migrations
|
|
15
|
+
6. **Context API surface** — changes to `ReferenceDataContext` shape affect all consumers of `useReferenceData()`
|
|
16
|
+
|
|
17
|
+
Output a verdict:
|
|
18
|
+
- **BREAKING** — consumers on the previous version will break on upgrade; needs a major version bump
|
|
19
|
+
- **NON-BREAKING** — new features or additions only; minor version bump appropriate
|
|
20
|
+
- **PATCH** — bug fixes with no API surface changes
|
|
21
|
+
|
|
22
|
+
Include specifics: which files changed, what the impact is, and the recommended semver bump.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PostToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Edit|Write",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node -e \"const i=JSON.parse(process.env.CLAUDE_TOOL_INPUT||'{}');const f=i.file_path||'';if((f.endsWith('.ts')||f.endsWith('.tsx'))&&!f.includes('dist/')){const cp=require('child_process');try{cp.execSync('npx biome format --write '+JSON.stringify(f),{stdio:'pipe',cwd:'c:/Users/Optiplex-BFHN/projects/court-calendar'})}catch(e){}}\""
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "command",
|
|
13
|
+
"command": "node -e \"const i=JSON.parse(process.env.CLAUDE_TOOL_INPUT||'{}');const f=i.file_path||'';if((f.endsWith('.ts')||f.endsWith('.tsx'))&&!f.includes('.test.')&&f.includes('src/')){const cp=require('child_process');try{cp.execSync('npx vitest run --related '+JSON.stringify(f),{stdio:'pipe',cwd:'c:/Users/Optiplex-BFHN/projects/court-calendar',timeout:30000})}catch(e){}}\""
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gen-test
|
|
3
|
+
description: Generate Vitest + React Testing Library tests for a court-calendar component or hook
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Generate tests for the `@propriety/court-calendar` library targeting the file or subject passed as $ARGUMENTS.
|
|
7
|
+
|
|
8
|
+
## Project Test Conventions
|
|
9
|
+
- Test files live in `src/__tests__/` named `<subject>.test.ts(x)`
|
|
10
|
+
- Shared fixtures: `src/__tests__/fixtures.ts` — read this first for reusable test data
|
|
11
|
+
- Shared helpers: `src/__tests__/helpers/`
|
|
12
|
+
- Hook test utilities: `src/__tests__/hooks/`
|
|
13
|
+
- Vitest globals (`describe`, `it`, `expect`, `vi`) are auto-imported — no import needed
|
|
14
|
+
- Use `renderHook` from `@testing-library/react` for custom hooks
|
|
15
|
+
- Mock context modules: `vi.mock('@/context/ReferenceDataContext', () => ({ useReferenceData: vi.fn(() => ({ allUsers: [], allHearingOfficers: [], allMuniNames: [], isLoading: false })) }))`
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
1. Read the target file to understand its props, return values, and side effects
|
|
19
|
+
2. Read `src/__tests__/fixtures.ts` for available court case / event test data
|
|
20
|
+
3. Scan `src/__tests__/helpers/` and `src/__tests__/hooks/` for setup utilities to reuse
|
|
21
|
+
4. Write tests covering: happy path, edge cases, null/undefined inputs, and loading states
|
|
22
|
+
5. Place output in `src/__tests__/<subject>.test.ts(x)`
|
|
23
|
+
|
|
24
|
+
Target: $ARGUMENTS
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release-prep
|
|
3
|
+
description: Prepare @propriety/court-calendar for npm publish — runs all checks, prompts for version bump, builds dist
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run the full release checklist for `@propriety/court-calendar`. Stop and report on first failure.
|
|
7
|
+
|
|
8
|
+
## Steps (sequential — stop on failure)
|
|
9
|
+
1. `npx tsc --noEmit` — type check must pass with zero errors
|
|
10
|
+
2. `npx vitest run` — all tests must pass
|
|
11
|
+
3. `npx biome check src/` — no lint or format violations
|
|
12
|
+
4. Ask the user: **patch / minor / major** version bump? (default: patch)
|
|
13
|
+
- Update the `version` field in `package.json` accordingly
|
|
14
|
+
5. `npm run build` — must succeed
|
|
15
|
+
6. Verify `dist/index.mjs` and `dist/index.d.ts` exist and are non-empty
|
|
16
|
+
7. Report: new version number, dist file sizes, ready to `npm publish`
|
|
17
|
+
|
|
18
|
+
Bump type from $ARGUMENTS (default: patch if not provided).
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# court-calendar
|
|
2
|
+
|
|
3
|
+
Published npm package: `@propriety/court-calendar` — a React component library built with Vite in library mode.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run build # tsc -b && vite build → dist/
|
|
9
|
+
npx vitest run # run all tests
|
|
10
|
+
npx biome format --write <file> # format a file
|
|
11
|
+
npx biome check src/ # lint entire src
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
- **Entry**: `src/index.ts` → exports `CCalendar` component from `src/_components/CCalendar.tsx`
|
|
17
|
+
- **Path alias**: `@/` resolves to `./src/`
|
|
18
|
+
- **Build output**: `dist/index.mjs` (ES module) + `dist/index.d.ts` (types)
|
|
19
|
+
- **Peer deps** (not bundled): React, react-dom, MUI 5, Emotion — `vite.config.ts` externalizes these
|
|
20
|
+
|
|
21
|
+
## Key Pattern: ReferenceDataContext
|
|
22
|
+
|
|
23
|
+
Reference data (users, hearing officers, municipalities) is fetched once in `ReferenceDataProvider` (wrapped inside `CCalendar`) and consumed via `useReferenceData()` in child components. Do **not** fetch this data at the module level or use top-level await.
|
|
24
|
+
|
|
25
|
+
Context is at `src/context/ReferenceDataContext.tsx` and provides: `allUsers`, `allHearingOfficers`, `allMuniNames`, `getTownshipName`, `getCountyName`, `isLoading`.
|
|
26
|
+
|
|
27
|
+
## Runtime Config
|
|
28
|
+
|
|
29
|
+
`import.meta.env` values are baked in at **library build time**, not consumer runtime. Pass runtime config (API keys, endpoints) as React props and thread them through context — never read env vars in library code.
|
|
30
|
+
|
|
31
|
+
## Component Props
|
|
32
|
+
|
|
33
|
+
`CCalendar` accepts:
|
|
34
|
+
- `apiKey` — runtime API key (passed through context, not env var)
|
|
35
|
+
- `activeUser` — the currently logged-in user object
|
|
36
|
+
|
|
37
|
+
## Tooling
|
|
38
|
+
|
|
39
|
+
- **Formatter/Linter**: Biome — not Prettier or ESLint
|
|
40
|
+
- **Tests**: Vitest + @testing-library/react. Files: `src/__tests__/`, fixtures: `src/__tests__/fixtures.ts`
|
|
41
|
+
- **Pre-commit**: Husky runs on commit — run `npx biome format --write` before staging
|
package/dist/index.mjs
CHANGED
|
@@ -12209,7 +12209,7 @@ function ur(t, e) {
|
|
|
12209
12209
|
return !1;
|
|
12210
12210
|
}
|
|
12211
12211
|
function H7(t, e) {
|
|
12212
|
-
return ur(t, e) ? !0 : t.evidence
|
|
12212
|
+
return ur(t, e) ? !0 : t.evidence != null && t.evidence.uploaded != null && t.evidence.uploaded.Evidence != null && t.evidence.uploaded.Evidence.trim() !== "";
|
|
12213
12213
|
}
|
|
12214
12214
|
function cu(t) {
|
|
12215
12215
|
return !t.evidence || !t.evidence.Evidence || t.evidence.Evidence === null || t.evidence.Evidence.trim() === "";
|
|
@@ -20697,7 +20697,7 @@ function N1({
|
|
|
20697
20697
|
bottom: 24,
|
|
20698
20698
|
left: "50%",
|
|
20699
20699
|
transform: "translateX(-50%)",
|
|
20700
|
-
zIndex:
|
|
20700
|
+
zIndex: 100,
|
|
20701
20701
|
pointerEvents: "none",
|
|
20702
20702
|
animation: v === "in" ? "lb-slide-up 0.3s cubic-bezier(0.34,1.56,0.64,1) forwards" : v === "out" ? "lb-slide-down 0.45s ease forwards" : "none"
|
|
20703
20703
|
},
|
|
@@ -20788,14 +20788,7 @@ function N1({
|
|
|
20788
20788
|
children: b
|
|
20789
20789
|
}
|
|
20790
20790
|
),
|
|
20791
|
-
/* @__PURE__ */ a(
|
|
20792
|
-
Zr,
|
|
20793
|
-
{
|
|
20794
|
-
size: 14,
|
|
20795
|
-
thickness: 5,
|
|
20796
|
-
sx: { color: "#8b5cf6", flexShrink: 0 }
|
|
20797
|
-
}
|
|
20798
|
-
)
|
|
20791
|
+
/* @__PURE__ */ a(Zr, { size: 14, thickness: 5, sx: { color: "#8b5cf6", flexShrink: 0 } })
|
|
20799
20792
|
]
|
|
20800
20793
|
}
|
|
20801
20794
|
),
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from
|
|
2
|
-
import humphreyImg from
|
|
3
|
-
import Box from
|
|
4
|
-
import Typography from
|
|
5
|
-
import CircularProgress from
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import humphreyImg from '@/assets/humphrey.png';
|
|
3
|
+
import Box from '@mui/material/Box';
|
|
4
|
+
import Typography from '@mui/material/Typography';
|
|
5
|
+
import CircularProgress from '@mui/material/CircularProgress';
|
|
6
6
|
|
|
7
7
|
interface LoadingBarProps {
|
|
8
8
|
isFetchingDates: boolean;
|
|
@@ -13,8 +13,7 @@ interface LoadingBarProps {
|
|
|
13
13
|
isFiltering?: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const GRADIENT =
|
|
17
|
-
"linear-gradient(135deg, #3b82f6 0%, #8b5cf6 50%, #06b6d4 100%)";
|
|
16
|
+
const GRADIENT = 'linear-gradient(135deg, #3b82f6 0%, #8b5cf6 50%, #06b6d4 100%)';
|
|
18
17
|
|
|
19
18
|
export default function LoadingBar({
|
|
20
19
|
isFetchingDates,
|
|
@@ -26,19 +25,19 @@ export default function LoadingBar({
|
|
|
26
25
|
}: LoadingBarProps) {
|
|
27
26
|
const isLoading = isFetchingDates || isFetchingCases || isSearching || isFiltering;
|
|
28
27
|
const [visible, setVisible] = useState(false);
|
|
29
|
-
const [animating, setAnimating] = useState<
|
|
28
|
+
const [animating, setAnimating] = useState<'in' | 'out' | 'idle'>('idle');
|
|
30
29
|
const hideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
31
30
|
|
|
32
31
|
useEffect(() => {
|
|
33
32
|
if (hideTimerRef.current) clearTimeout(hideTimerRef.current);
|
|
34
33
|
if (isLoading) {
|
|
35
34
|
setVisible(true);
|
|
36
|
-
setAnimating(
|
|
35
|
+
setAnimating('in');
|
|
37
36
|
} else {
|
|
38
|
-
setAnimating(
|
|
37
|
+
setAnimating('out');
|
|
39
38
|
hideTimerRef.current = setTimeout(() => {
|
|
40
39
|
setVisible(false);
|
|
41
|
-
setAnimating(
|
|
40
|
+
setAnimating('idle');
|
|
42
41
|
}, 500);
|
|
43
42
|
}
|
|
44
43
|
return () => {
|
|
@@ -54,14 +53,14 @@ export default function LoadingBar({
|
|
|
54
53
|
: Math.min(loadedCount / totalCount, 1);
|
|
55
54
|
|
|
56
55
|
const label = isFetchingDates
|
|
57
|
-
?
|
|
56
|
+
? 'Loading court dates…'
|
|
58
57
|
: isSearching
|
|
59
|
-
?
|
|
58
|
+
? 'Searching…'
|
|
60
59
|
: isFiltering && !isFetchingCases
|
|
61
|
-
?
|
|
60
|
+
? 'Filtering…'
|
|
62
61
|
: progress !== null
|
|
63
62
|
? `Loading cases — ${loadedCount} / ${totalCount}`
|
|
64
|
-
:
|
|
63
|
+
: 'Loading…';
|
|
65
64
|
|
|
66
65
|
return (
|
|
67
66
|
<>
|
|
@@ -69,48 +68,47 @@ export default function LoadingBar({
|
|
|
69
68
|
{/* Outer positioning wrapper */}
|
|
70
69
|
<Box
|
|
71
70
|
sx={{
|
|
72
|
-
position:
|
|
71
|
+
position: 'fixed',
|
|
73
72
|
bottom: 24,
|
|
74
|
-
left:
|
|
75
|
-
transform:
|
|
76
|
-
zIndex:
|
|
77
|
-
pointerEvents:
|
|
73
|
+
left: '50%',
|
|
74
|
+
transform: 'translateX(-50%)',
|
|
75
|
+
zIndex: 100,
|
|
76
|
+
pointerEvents: 'none',
|
|
78
77
|
animation:
|
|
79
|
-
animating ===
|
|
80
|
-
?
|
|
81
|
-
: animating ===
|
|
82
|
-
?
|
|
83
|
-
:
|
|
78
|
+
animating === 'in'
|
|
79
|
+
? 'lb-slide-up 0.3s cubic-bezier(0.34,1.56,0.64,1) forwards'
|
|
80
|
+
: animating === 'out'
|
|
81
|
+
? 'lb-slide-down 0.45s ease forwards'
|
|
82
|
+
: 'none',
|
|
84
83
|
}}
|
|
85
84
|
>
|
|
86
85
|
{/* Gradient border shell + glow shadow */}
|
|
87
86
|
<Box
|
|
88
87
|
sx={{
|
|
89
88
|
background: GRADIENT,
|
|
90
|
-
borderRadius:
|
|
91
|
-
p:
|
|
89
|
+
borderRadius: '26px',
|
|
90
|
+
p: '1.5px',
|
|
92
91
|
boxShadow:
|
|
93
|
-
|
|
92
|
+
'0 0 18px 2px rgba(139,92,246,0.35), 0 0 8px 1px rgba(59,130,246,0.25), 0 4px 16px rgba(0,0,0,0.18)',
|
|
94
93
|
}}
|
|
95
94
|
>
|
|
96
95
|
{/* Inner content card */}
|
|
97
96
|
<Box
|
|
98
97
|
sx={{
|
|
99
|
-
backgroundColor:
|
|
100
|
-
borderRadius:
|
|
98
|
+
backgroundColor: 'var(--bg)',
|
|
99
|
+
borderRadius: '24px',
|
|
101
100
|
px: 2.25,
|
|
102
101
|
pt: 1,
|
|
103
102
|
pb: progress !== null ? 1.25 : 1,
|
|
104
103
|
minWidth: 220,
|
|
105
104
|
maxWidth: 340,
|
|
106
|
-
fontFamily:
|
|
107
|
-
'"Roboto","Helvetica","Arial",sans-serif',
|
|
105
|
+
fontFamily: '"Roboto","Helvetica","Arial",sans-serif',
|
|
108
106
|
}}
|
|
109
107
|
>
|
|
110
108
|
<Box
|
|
111
109
|
sx={{
|
|
112
|
-
display:
|
|
113
|
-
alignItems:
|
|
110
|
+
display: 'flex',
|
|
111
|
+
alignItems: 'center',
|
|
114
112
|
gap: 1,
|
|
115
113
|
mb: progress !== null ? 0.75 : 0,
|
|
116
114
|
}}
|
|
@@ -118,7 +116,7 @@ export default function LoadingBar({
|
|
|
118
116
|
{/* Humphrey avatar with gradient ring */}
|
|
119
117
|
<Box
|
|
120
118
|
sx={{
|
|
121
|
-
position:
|
|
119
|
+
position: 'relative',
|
|
122
120
|
flexShrink: 0,
|
|
123
121
|
width: 28,
|
|
124
122
|
height: 28,
|
|
@@ -126,43 +124,39 @@ export default function LoadingBar({
|
|
|
126
124
|
>
|
|
127
125
|
<Box
|
|
128
126
|
sx={{
|
|
129
|
-
position:
|
|
127
|
+
position: 'absolute',
|
|
130
128
|
inset: -1.5,
|
|
131
|
-
borderRadius:
|
|
129
|
+
borderRadius: '50%',
|
|
132
130
|
background: GRADIENT,
|
|
133
131
|
zIndex: 0,
|
|
134
132
|
}}
|
|
135
133
|
/>
|
|
136
134
|
<Box
|
|
137
|
-
component=
|
|
135
|
+
component='img'
|
|
138
136
|
src={humphreyImg}
|
|
139
137
|
sx={{
|
|
140
|
-
position:
|
|
138
|
+
position: 'relative',
|
|
141
139
|
zIndex: 1,
|
|
142
140
|
width: 28,
|
|
143
141
|
height: 28,
|
|
144
|
-
borderRadius:
|
|
145
|
-
objectFit:
|
|
146
|
-
border:
|
|
142
|
+
borderRadius: '50%',
|
|
143
|
+
objectFit: 'cover',
|
|
144
|
+
border: '1.5px solid var(--bg)',
|
|
147
145
|
}}
|
|
148
146
|
/>
|
|
149
147
|
</Box>
|
|
150
148
|
<Typography
|
|
151
|
-
variant=
|
|
149
|
+
variant='body2'
|
|
152
150
|
sx={{
|
|
153
151
|
fontWeight: 500,
|
|
154
|
-
whiteSpace:
|
|
155
|
-
color:
|
|
152
|
+
whiteSpace: 'nowrap',
|
|
153
|
+
color: 'var(--text)',
|
|
156
154
|
fontSize: 13,
|
|
157
155
|
}}
|
|
158
156
|
>
|
|
159
157
|
{label}
|
|
160
158
|
</Typography>
|
|
161
|
-
<CircularProgress
|
|
162
|
-
size={14}
|
|
163
|
-
thickness={5}
|
|
164
|
-
sx={{ color: "#8b5cf6", flexShrink: 0 }}
|
|
165
|
-
/>
|
|
159
|
+
<CircularProgress size={14} thickness={5} sx={{ color: '#8b5cf6', flexShrink: 0 }} />
|
|
166
160
|
</Box>
|
|
167
161
|
|
|
168
162
|
{progress !== null && (
|
|
@@ -170,17 +164,17 @@ export default function LoadingBar({
|
|
|
170
164
|
sx={{
|
|
171
165
|
height: 4,
|
|
172
166
|
borderRadius: 1,
|
|
173
|
-
backgroundColor:
|
|
174
|
-
overflow:
|
|
167
|
+
backgroundColor: 'var(--fc-border-color)',
|
|
168
|
+
overflow: 'hidden',
|
|
175
169
|
}}
|
|
176
170
|
>
|
|
177
171
|
<Box
|
|
178
172
|
sx={{
|
|
179
|
-
height:
|
|
173
|
+
height: '100%',
|
|
180
174
|
width: `${progress * 100}%`,
|
|
181
175
|
borderRadius: 1,
|
|
182
176
|
background: GRADIENT,
|
|
183
|
-
transition:
|
|
177
|
+
transition: 'width 0.3s ease',
|
|
184
178
|
}}
|
|
185
179
|
/>
|
|
186
180
|
</Box>
|
package/src/helpers/cases.ts
CHANGED
|
@@ -19,7 +19,7 @@ export function isCaseSettled(c: Case, isVillage: boolean): boolean {
|
|
|
19
19
|
|
|
20
20
|
export function isCaseUploadedOrSettled(c: Case, isVillage: boolean): boolean {
|
|
21
21
|
if (isCaseSettled(c, isVillage)) return true;
|
|
22
|
-
return c.evidence
|
|
22
|
+
return c.evidence != null && c.evidence.uploaded != null && c.evidence.uploaded.Evidence != null && c.evidence.uploaded.Evidence.trim() !== "";
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function isCaseMissingEvidence(c: Case): boolean {
|