@chesapeake/stack 0.1.4 → 0.1.6
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/CHANGELOG.md +16 -0
- package/components/ux/focus/EventCard.tsx +48 -0
- package/components/ux/focus/FocusCard.tsx +37 -0
- package/components/ux/focus/FocusFeed.tsx +39 -0
- package/components/ux/focus/TodoCard.tsx +88 -0
- package/components/ux/focus/index.ts +8 -0
- package/components/ux/index.ts +1 -0
- package/index.ts +1 -0
- package/lib/githubIssue.ts +186 -0
- package/package.json +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
|
6
6
|
and this project adheres to
|
|
7
7
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [0.1.6] - 2026-09-19
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Neutral `FocusCard`, `FocusFeed`, `TodoCard`, and `EventCard` components for
|
|
14
|
+
mixed todo and calendar-style feeds
|
|
15
|
+
- Public focus component subpath exports
|
|
16
|
+
|
|
17
|
+
## [0.1.5] - 2026-08-03
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `lib/githubIssue`: product-neutral GitHub issue URL parsing, PR rejection,
|
|
22
|
+
snapshot normalization, and import keys
|
|
23
|
+
|
|
9
24
|
## [0.1.4] - 2026-08-03
|
|
10
25
|
|
|
11
26
|
### Added
|
|
@@ -76,6 +91,7 @@ and this project adheres to
|
|
|
76
91
|
- Apache 2.0 license
|
|
77
92
|
|
|
78
93
|
[0.1.4]: https://github.com/chesapeakedev/stack/releases/tag/v0.1.4
|
|
94
|
+
[0.1.6]: https://github.com/chesapeakedev/stack/releases/tag/v0.1.6
|
|
79
95
|
[0.1.3]: https://github.com/chesapeakedev/stack/releases/tag/v0.1.3
|
|
80
96
|
[0.1.2]: https://github.com/chesapeakedev/stack/releases/tag/v0.1.2
|
|
81
97
|
[0.1.1]: https://github.com/chesapeakedev/stack/releases/tag/v0.1.1
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Copyright 2026 Chesapeake Computing
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
import { FocusCard } from "./FocusCard.tsx";
|
|
6
|
+
|
|
7
|
+
export interface EventCardProps {
|
|
8
|
+
title: string;
|
|
9
|
+
time: ReactNode;
|
|
10
|
+
location?: ReactNode;
|
|
11
|
+
metadata?: ReactNode;
|
|
12
|
+
actions?: ReactNode;
|
|
13
|
+
allDay?: boolean;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Read-only calendar event presentation; consumers own calendar mutations. */
|
|
18
|
+
export function EventCard({
|
|
19
|
+
title,
|
|
20
|
+
time,
|
|
21
|
+
location,
|
|
22
|
+
metadata,
|
|
23
|
+
actions,
|
|
24
|
+
allDay = false,
|
|
25
|
+
className,
|
|
26
|
+
}: EventCardProps) {
|
|
27
|
+
return (
|
|
28
|
+
<FocusCard
|
|
29
|
+
className={className}
|
|
30
|
+
leading={
|
|
31
|
+
<div
|
|
32
|
+
aria-hidden="true"
|
|
33
|
+
className="mt-1 h-5 w-5 shrink-0 rounded-full border-2 border-primary/60"
|
|
34
|
+
/>
|
|
35
|
+
}
|
|
36
|
+
actions={actions}
|
|
37
|
+
>
|
|
38
|
+
<div className="space-y-1">
|
|
39
|
+
<div className="font-medium">{title}</div>
|
|
40
|
+
<div className="text-sm text-muted-foreground">
|
|
41
|
+
{allDay ? "All day" : time}
|
|
42
|
+
</div>
|
|
43
|
+
{location}
|
|
44
|
+
{metadata}
|
|
45
|
+
</div>
|
|
46
|
+
</FocusCard>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Copyright 2026 Chesapeake Computing
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
import { Card } from "../../ui/card.tsx";
|
|
6
|
+
|
|
7
|
+
export interface FocusCardProps {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
leading?: ReactNode;
|
|
10
|
+
actions?: ReactNode;
|
|
11
|
+
completed?: boolean;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Shared surface and layout for every item in a focus feed. */
|
|
16
|
+
export function FocusCard({
|
|
17
|
+
children,
|
|
18
|
+
leading,
|
|
19
|
+
actions,
|
|
20
|
+
completed = false,
|
|
21
|
+
className,
|
|
22
|
+
}: FocusCardProps) {
|
|
23
|
+
return (
|
|
24
|
+
<Card
|
|
25
|
+
role="listitem"
|
|
26
|
+
className={`bg-background min-w-0 p-4 transition-shadow hover:shadow-md ${
|
|
27
|
+
completed ? "bg-muted/50 opacity-75" : ""
|
|
28
|
+
} ${className ?? ""}`}
|
|
29
|
+
>
|
|
30
|
+
<div className="flex min-w-0 items-start gap-3">
|
|
31
|
+
{leading}
|
|
32
|
+
<div className="min-w-0 flex-1">{children}</div>
|
|
33
|
+
{actions}
|
|
34
|
+
</div>
|
|
35
|
+
</Card>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Copyright 2026 Chesapeake Computing
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
|
|
6
|
+
export interface FocusFeedProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
loading?: boolean;
|
|
9
|
+
empty?: ReactNode;
|
|
10
|
+
className?: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** A narrow, accessible feed surface for mixed todo and event cards. */
|
|
15
|
+
export function FocusFeed({
|
|
16
|
+
children,
|
|
17
|
+
loading = false,
|
|
18
|
+
empty,
|
|
19
|
+
className,
|
|
20
|
+
label = "Focus feed",
|
|
21
|
+
}: FocusFeedProps) {
|
|
22
|
+
const content = loading ? (
|
|
23
|
+
<div className="text-muted-foreground p-8 text-center" role="status">
|
|
24
|
+
Loading feed...
|
|
25
|
+
</div>
|
|
26
|
+
) : (
|
|
27
|
+
(empty ?? children)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
aria-label={label}
|
|
33
|
+
className={`flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto p-4 ${className ?? ""}`}
|
|
34
|
+
role="list"
|
|
35
|
+
>
|
|
36
|
+
{content}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Copyright 2026 Chesapeake Computing
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import { Input } from "../../ui/input.tsx";
|
|
7
|
+
import { FocusCard } from "./FocusCard.tsx";
|
|
8
|
+
|
|
9
|
+
export interface TodoCardProps {
|
|
10
|
+
title: string;
|
|
11
|
+
completed: boolean;
|
|
12
|
+
onToggle: () => void;
|
|
13
|
+
onTitleChange?: (title: string) => void;
|
|
14
|
+
metadata?: ReactNode;
|
|
15
|
+
description?: ReactNode;
|
|
16
|
+
actions?: ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function TodoCard({
|
|
21
|
+
title,
|
|
22
|
+
completed,
|
|
23
|
+
onToggle,
|
|
24
|
+
onTitleChange,
|
|
25
|
+
metadata,
|
|
26
|
+
description,
|
|
27
|
+
actions,
|
|
28
|
+
className,
|
|
29
|
+
}: TodoCardProps) {
|
|
30
|
+
const [editing, setEditing] = useState(false);
|
|
31
|
+
const [draft, setDraft] = useState(title);
|
|
32
|
+
|
|
33
|
+
const save = () => {
|
|
34
|
+
const next = draft.trim();
|
|
35
|
+
if (next && next !== title) onTitleChange?.(next);
|
|
36
|
+
setEditing(false);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<FocusCard
|
|
41
|
+
completed={completed}
|
|
42
|
+
className={className}
|
|
43
|
+
leading={
|
|
44
|
+
<input
|
|
45
|
+
aria-label={completed ? "Mark incomplete" : "Mark complete"}
|
|
46
|
+
checked={completed}
|
|
47
|
+
className="mt-1 h-5 w-5 shrink-0 cursor-pointer rounded text-primary focus:ring-2 focus:ring-primary"
|
|
48
|
+
onChange={onToggle}
|
|
49
|
+
type="checkbox"
|
|
50
|
+
/>
|
|
51
|
+
}
|
|
52
|
+
actions={actions}
|
|
53
|
+
>
|
|
54
|
+
<div className="space-y-1">
|
|
55
|
+
{editing ? (
|
|
56
|
+
<Input
|
|
57
|
+
aria-label="Edit todo title"
|
|
58
|
+
autoFocus
|
|
59
|
+
onBlur={save}
|
|
60
|
+
onChange={(event) => {
|
|
61
|
+
setDraft(event.target.value);
|
|
62
|
+
}}
|
|
63
|
+
onKeyDown={(event) => {
|
|
64
|
+
if (event.key === "Enter") save();
|
|
65
|
+
if (event.key === "Escape") setEditing(false);
|
|
66
|
+
}}
|
|
67
|
+
value={draft}
|
|
68
|
+
/>
|
|
69
|
+
) : (
|
|
70
|
+
<button
|
|
71
|
+
className={`block w-full rounded-sm text-left font-medium hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${
|
|
72
|
+
completed ? "text-muted-foreground line-through" : ""
|
|
73
|
+
}`}
|
|
74
|
+
onClick={() => {
|
|
75
|
+
setDraft(title);
|
|
76
|
+
if (onTitleChange) setEditing(true);
|
|
77
|
+
}}
|
|
78
|
+
type="button"
|
|
79
|
+
>
|
|
80
|
+
{title}
|
|
81
|
+
</button>
|
|
82
|
+
)}
|
|
83
|
+
{metadata}
|
|
84
|
+
{description}
|
|
85
|
+
</div>
|
|
86
|
+
</FocusCard>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { EventCard } from "./EventCard.tsx";
|
|
2
|
+
export type { EventCardProps } from "./EventCard.tsx";
|
|
3
|
+
export { FocusCard } from "./FocusCard.tsx";
|
|
4
|
+
export type { FocusCardProps } from "./FocusCard.tsx";
|
|
5
|
+
export { FocusFeed } from "./FocusFeed.tsx";
|
|
6
|
+
export type { FocusFeedProps } from "./FocusFeed.tsx";
|
|
7
|
+
export { TodoCard } from "./TodoCard.tsx";
|
|
8
|
+
export type { TodoCardProps } from "./TodoCard.tsx";
|
package/components/ux/index.ts
CHANGED
package/index.ts
CHANGED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// Copyright 2026 Chesapeake Computing
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Product-neutral GitHub issue helpers shared by browser apps (denoise-ui, void-ui).
|
|
6
|
+
* Does not call GitHub or any product API — parse, validate, and normalize only.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface ParsedGitHubIssueRef {
|
|
10
|
+
owner: string;
|
|
11
|
+
repo: string;
|
|
12
|
+
number: number;
|
|
13
|
+
/** Canonical https://github.com/{owner}/{repo}/issues/{number} URL. */
|
|
14
|
+
url: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface GitHubIssueLabel {
|
|
18
|
+
id?: number;
|
|
19
|
+
name: string;
|
|
20
|
+
color?: string;
|
|
21
|
+
description?: string | null;
|
|
22
|
+
default?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Minimal issue snapshot suitable for local task persistence. */
|
|
26
|
+
export interface GitHubIssueSnapshot {
|
|
27
|
+
number: number;
|
|
28
|
+
title: string;
|
|
29
|
+
body: string | null;
|
|
30
|
+
state: "open" | "closed";
|
|
31
|
+
html_url: string;
|
|
32
|
+
labels: GitHubIssueLabel[];
|
|
33
|
+
created_at: string;
|
|
34
|
+
updated_at: string;
|
|
35
|
+
closed_at: string | null;
|
|
36
|
+
owner: string;
|
|
37
|
+
repo: string;
|
|
38
|
+
repository: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ISSUE_PATH =
|
|
42
|
+
/^\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/issues\/([1-9]\d*)\/?$/;
|
|
43
|
+
|
|
44
|
+
const PULL_PATH =
|
|
45
|
+
/^\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/pull\/([1-9]\d*)\/?$/;
|
|
46
|
+
|
|
47
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
48
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parse a GitHub issue URL into owner/repo/number.
|
|
53
|
+
* Rejects pull-request URLs (`/pull/N`) and non-github.com hosts.
|
|
54
|
+
*/
|
|
55
|
+
export function parseGitHubIssueUrl(
|
|
56
|
+
value: string
|
|
57
|
+
): ParsedGitHubIssueRef | null {
|
|
58
|
+
const trimmed = value.trim();
|
|
59
|
+
if (trimmed === "") return null;
|
|
60
|
+
|
|
61
|
+
let url: URL;
|
|
62
|
+
try {
|
|
63
|
+
url = new URL(trimmed);
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (url.protocol !== "https:" || url.hostname !== "github.com") {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (PULL_PATH.test(url.pathname)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const match = ISSUE_PATH.exec(url.pathname);
|
|
77
|
+
if (!match) return null;
|
|
78
|
+
|
|
79
|
+
const owner = match[1];
|
|
80
|
+
const repo = match[2];
|
|
81
|
+
const number = Number(match[3]);
|
|
82
|
+
if (!Number.isSafeInteger(number) || number < 1) return null;
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
owner,
|
|
86
|
+
repo,
|
|
87
|
+
number,
|
|
88
|
+
url: canonicalGitHubIssueUrl(owner, repo, number),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function canonicalGitHubIssueUrl(
|
|
93
|
+
owner: string,
|
|
94
|
+
repo: string,
|
|
95
|
+
number: number
|
|
96
|
+
): string {
|
|
97
|
+
return `https://github.com/${owner}/${repo}/issues/${String(number)}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function githubIssueImportKey(
|
|
101
|
+
owner: string,
|
|
102
|
+
repo: string,
|
|
103
|
+
number: number
|
|
104
|
+
): string {
|
|
105
|
+
return `${owner}/${repo}#${String(number)}`.toLowerCase();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** True when a GitHub Issues API payload represents a pull request. */
|
|
109
|
+
export function isGitHubPullRequestPayload(value: unknown): boolean {
|
|
110
|
+
if (!isRecord(value)) return false;
|
|
111
|
+
return value.pull_request != null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function isValidRepoName(value: string): boolean {
|
|
115
|
+
return /^[A-Za-z0-9_.-]+$/.test(value) && value.length > 0 && value.length <= 100;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function isValidIssueNumber(value: unknown): value is number {
|
|
119
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function parseLabels(value: unknown): GitHubIssueLabel[] {
|
|
123
|
+
if (!Array.isArray(value)) return [];
|
|
124
|
+
const labels: GitHubIssueLabel[] = [];
|
|
125
|
+
for (const item of value) {
|
|
126
|
+
if (!isRecord(item) || typeof item.name !== "string") continue;
|
|
127
|
+
labels.push({
|
|
128
|
+
...(typeof item.id === "number" ? { id: item.id } : {}),
|
|
129
|
+
name: item.name,
|
|
130
|
+
...(typeof item.color === "string" ? { color: item.color } : {}),
|
|
131
|
+
...(item.description === null || typeof item.description === "string"
|
|
132
|
+
? { description: item.description }
|
|
133
|
+
: {}),
|
|
134
|
+
...(typeof item.default === "boolean" ? { default: item.default } : {}),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return labels;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Normalize a GitHub Issues API payload (or a Void API response) into a
|
|
142
|
+
* snapshot. Returns null for pull requests or malformed payloads.
|
|
143
|
+
*/
|
|
144
|
+
export function normalizeGitHubIssue(
|
|
145
|
+
value: unknown,
|
|
146
|
+
owner: string,
|
|
147
|
+
repo: string
|
|
148
|
+
): GitHubIssueSnapshot | null {
|
|
149
|
+
if (!isRecord(value)) return null;
|
|
150
|
+
if (isGitHubPullRequestPayload(value)) return null;
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
typeof value.number !== "number" ||
|
|
154
|
+
!isValidIssueNumber(value.number) ||
|
|
155
|
+
typeof value.title !== "string" ||
|
|
156
|
+
(value.state !== "open" && value.state !== "closed") ||
|
|
157
|
+
typeof value.html_url !== "string" ||
|
|
158
|
+
value.html_url.trim() === "" ||
|
|
159
|
+
typeof value.created_at !== "string" ||
|
|
160
|
+
typeof value.updated_at !== "string"
|
|
161
|
+
) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const body =
|
|
166
|
+
value.body === null || typeof value.body === "string" ? value.body : null;
|
|
167
|
+
const closedAt =
|
|
168
|
+
value.closed_at === null || typeof value.closed_at === "string"
|
|
169
|
+
? value.closed_at
|
|
170
|
+
: null;
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
number: value.number,
|
|
174
|
+
title: value.title,
|
|
175
|
+
body,
|
|
176
|
+
state: value.state,
|
|
177
|
+
html_url: value.html_url,
|
|
178
|
+
labels: parseLabels(value.labels),
|
|
179
|
+
created_at: value.created_at,
|
|
180
|
+
updated_at: value.updated_at,
|
|
181
|
+
closed_at: closedAt,
|
|
182
|
+
owner,
|
|
183
|
+
repo,
|
|
184
|
+
repository: `${owner}/${repo}`,
|
|
185
|
+
};
|
|
186
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chesapeake/stack",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "A simple, reusable React component library and design system built on shadcn/ui with Tailwind CSS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@10.2.0",
|
|
@@ -111,6 +111,11 @@
|
|
|
111
111
|
"./components/ux/task/TaskInput": "./components/ux/task/TaskInput.tsx",
|
|
112
112
|
"./components/ux/task/TaskCard": "./components/ux/task/TaskCard.tsx",
|
|
113
113
|
"./components/ux/task/TaskList": "./components/ux/task/TaskList.tsx",
|
|
114
|
+
"./components/ux/focus": "./components/ux/focus/index.ts",
|
|
115
|
+
"./components/ux/focus/FocusCard": "./components/ux/focus/FocusCard.tsx",
|
|
116
|
+
"./components/ux/focus/FocusFeed": "./components/ux/focus/FocusFeed.tsx",
|
|
117
|
+
"./components/ux/focus/TodoCard": "./components/ux/focus/TodoCard.tsx",
|
|
118
|
+
"./components/ux/focus/EventCard": "./components/ux/focus/EventCard.tsx",
|
|
114
119
|
"./components/ux/connectivity": "./components/ux/connectivity/index.ts",
|
|
115
120
|
"./components/ux/connectivity/ConnectivityBadge": "./components/ux/connectivity/ConnectivityBadge.tsx",
|
|
116
121
|
"./components/ux/runner": "./components/ux/runner/index.ts",
|
|
@@ -123,6 +128,7 @@
|
|
|
123
128
|
"./components/ux/auth/github/AuthCallback": "./components/ux/auth/github/AuthCallback.tsx",
|
|
124
129
|
"./components/ux/auth/github/types": "./components/ux/auth/github/types.ts",
|
|
125
130
|
"./lib/utils": "./lib/utils.ts",
|
|
131
|
+
"./lib/githubIssue": "./lib/githubIssue.ts",
|
|
126
132
|
"./types/user": "./types/user.ts",
|
|
127
133
|
"./styles": "./styles/index.css",
|
|
128
134
|
"./tailwind.preset": "./tailwind.preset.ts",
|