@cosmicdrift/kumiko-bundled-features 0.224.2 → 0.226.0
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/package.json +11 -9
- package/src/admin-shell/__tests__/admin-shell.boot.test.ts +66 -0
- package/src/admin-shell/feature.ts +30 -3
- package/src/admin-shell/i18n.ts +2 -0
- package/src/cap-overview/__tests__/cap-overview-security.integration.test.ts +207 -0
- package/src/cap-overview/__tests__/cap-overview.boot.test.ts +120 -0
- package/src/cap-overview/__tests__/usage-math.test.ts +60 -0
- package/src/cap-overview/changes.json +8 -0
- package/src/cap-overview/constants.ts +26 -0
- package/src/cap-overview/feature.ts +77 -0
- package/src/cap-overview/handlers/caps-usage.query.ts +99 -0
- package/src/cap-overview/handlers/tenant-caps-list.query.ts +230 -0
- package/src/cap-overview/handlers/tenant-options.query.ts +33 -0
- package/src/cap-overview/i18n.ts +26 -0
- package/src/cap-overview/index.ts +20 -0
- package/src/cap-overview/screens.ts +101 -0
- package/src/cap-overview/types.ts +58 -0
- package/src/cap-overview/usage-math.ts +32 -0
- package/src/cap-overview/web/__tests__/cap-cards-panel.test.tsx +244 -0
- package/src/cap-overview/web/__tests__/cap-usage-bar.test.tsx +76 -0
- package/src/cap-overview/web/cap-cards-panel.tsx +109 -0
- package/src/cap-overview/web/cap-usage-bar.tsx +47 -0
- package/src/cap-overview/web/cap-usage-cell.tsx +20 -0
- package/src/cap-overview/web/client-plugin.tsx +27 -0
- package/src/cap-overview/web/index.ts +10 -0
- package/src/form-draft/__tests__/cleanup.integration.test.ts +51 -20
- package/src/notes-history/__tests__/notes-section.test.tsx +22 -11
- package/src/notes-history/web/notes-section.tsx +2 -2
- package/src/sessions/handlers/detail.query.ts +11 -0
- package/src/sessions/handlers/list.query.ts +14 -0
|
@@ -131,12 +131,12 @@ describe("NotesSection — entry display", () => {
|
|
|
131
131
|
test("note body and meta line render as two distinct nodes, not one run-on line", async () => {
|
|
132
132
|
const view = renderSection();
|
|
133
133
|
await waitFor(() => {
|
|
134
|
-
if (view.queryByTestId("notes-section-
|
|
134
|
+
if (view.queryByTestId("notes-section-body-note-1") === null) {
|
|
135
135
|
throw new Error("row not rendered yet");
|
|
136
136
|
}
|
|
137
137
|
});
|
|
138
|
-
const bodyEl = view.getByTestId("notes-section-
|
|
139
|
-
const metaEl = view.getByTestId("notes-section-
|
|
138
|
+
const bodyEl = view.getByTestId("notes-section-body-note-1");
|
|
139
|
+
const metaEl = view.getByTestId("notes-section-meta-note-1");
|
|
140
140
|
expect(bodyEl).not.toBe(metaEl);
|
|
141
141
|
expect(bodyEl.textContent).toBe(NOTE_BODY);
|
|
142
142
|
expect(metaEl.textContent).not.toContain(NOTE_BODY);
|
|
@@ -145,14 +145,25 @@ describe("NotesSection — entry display", () => {
|
|
|
145
145
|
expect(row.className).toContain("flex-col");
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
+
test("the row testid prefix matches exactly one node per note, not three", async () => {
|
|
149
|
+
const view = renderSection();
|
|
150
|
+
await waitFor(() => {
|
|
151
|
+
if (view.queryByTestId("notes-section-row-note-1") === null) {
|
|
152
|
+
throw new Error("rows not rendered yet");
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
const matches = view.container.querySelectorAll('[data-testid^="notes-section-row-"]');
|
|
156
|
+
expect(matches.length).toBe(NOTE_ROWS.length);
|
|
157
|
+
});
|
|
158
|
+
|
|
148
159
|
test("a stamped authorName renders as the author, never the raw authorId", async () => {
|
|
149
160
|
const view = renderSection();
|
|
150
161
|
await waitFor(() => {
|
|
151
|
-
if (view.queryByTestId("notes-section-
|
|
162
|
+
if (view.queryByTestId("notes-section-meta-note-1") === null) {
|
|
152
163
|
throw new Error("row not rendered yet");
|
|
153
164
|
}
|
|
154
165
|
});
|
|
155
|
-
const metaEl = view.getByTestId("notes-section-
|
|
166
|
+
const metaEl = view.getByTestId("notes-section-meta-note-1");
|
|
156
167
|
expect(metaEl.textContent).not.toContain(AUTHOR_ID);
|
|
157
168
|
expect(metaEl.textContent).toContain(AUTHOR_NAME);
|
|
158
169
|
});
|
|
@@ -160,22 +171,22 @@ describe("NotesSection — entry display", () => {
|
|
|
160
171
|
test("a null authorName (legacy row) renders the placeholder", async () => {
|
|
161
172
|
const view = renderSection();
|
|
162
173
|
await waitFor(() => {
|
|
163
|
-
if (view.queryByTestId("notes-section-
|
|
174
|
+
if (view.queryByTestId("notes-section-meta-note-2") === null) {
|
|
164
175
|
throw new Error("row not rendered yet");
|
|
165
176
|
}
|
|
166
177
|
});
|
|
167
|
-
const metaEl = view.getByTestId("notes-section-
|
|
178
|
+
const metaEl = view.getByTestId("notes-section-meta-note-2");
|
|
168
179
|
expect(metaEl.textContent).toContain("Unknown author");
|
|
169
180
|
});
|
|
170
181
|
|
|
171
182
|
test("a shredded authorName sentinel renders the placeholder, never [[erased]]", async () => {
|
|
172
183
|
const view = renderSection();
|
|
173
184
|
await waitFor(() => {
|
|
174
|
-
if (view.queryByTestId("notes-section-
|
|
185
|
+
if (view.queryByTestId("notes-section-meta-note-3") === null) {
|
|
175
186
|
throw new Error("row not rendered yet");
|
|
176
187
|
}
|
|
177
188
|
});
|
|
178
|
-
const metaEl = view.getByTestId("notes-section-
|
|
189
|
+
const metaEl = view.getByTestId("notes-section-meta-note-3");
|
|
179
190
|
expect(metaEl.textContent).toContain("Unknown author");
|
|
180
191
|
expect(metaEl.textContent).not.toContain(PII_ERASED_SENTINEL);
|
|
181
192
|
});
|
|
@@ -183,11 +194,11 @@ describe("NotesSection — entry display", () => {
|
|
|
183
194
|
test("date renders formatted, never the raw ISO timestamp", async () => {
|
|
184
195
|
const view = renderSection();
|
|
185
196
|
await waitFor(() => {
|
|
186
|
-
if (view.queryByTestId("notes-section-
|
|
197
|
+
if (view.queryByTestId("notes-section-meta-note-1") === null) {
|
|
187
198
|
throw new Error("row not rendered yet");
|
|
188
199
|
}
|
|
189
200
|
});
|
|
190
|
-
const metaEl = view.getByTestId("notes-section-
|
|
201
|
+
const metaEl = view.getByTestId("notes-section-meta-note-1");
|
|
191
202
|
expect(metaEl.textContent).not.toContain(INSERTED_AT);
|
|
192
203
|
expect(metaEl.textContent).toContain(formatWhen(INSERTED_AT));
|
|
193
204
|
});
|
|
@@ -149,8 +149,8 @@ export function NotesSection({
|
|
|
149
149
|
data-testid={`notes-section-row-${n.id}`}
|
|
150
150
|
className="flex flex-col gap-1 rounded-md border p-2"
|
|
151
151
|
>
|
|
152
|
-
<Text testId={`notes-section-
|
|
153
|
-
<Text variant="small" testId={`notes-section-
|
|
152
|
+
<Text testId={`notes-section-body-${n.id}`}>{n.body}</Text>
|
|
153
|
+
<Text variant="small" testId={`notes-section-meta-${n.id}`}>
|
|
154
154
|
{t("notesHistory.section.meta", {
|
|
155
155
|
author: displayAuthorName(n.authorName, t("notesHistory.section.authorUnknown")),
|
|
156
156
|
date: formatWhen(n.insertedAt),
|
|
@@ -6,10 +6,21 @@ import { userSessionTable } from "../schema/user-session";
|
|
|
6
6
|
|
|
7
7
|
// Admin single-session inspector — mirrors list.query's decrypt handling for
|
|
8
8
|
// the one-row case. ctx.db (TenantDb) applies tenant-scoping automatically.
|
|
9
|
+
const sessionRowSchema = z.object({
|
|
10
|
+
id: z.string(),
|
|
11
|
+
userId: z.string(),
|
|
12
|
+
createdAt: z.unknown(),
|
|
13
|
+
expiresAt: z.unknown(),
|
|
14
|
+
revokedAt: z.unknown(),
|
|
15
|
+
ip: z.string().nullable(),
|
|
16
|
+
userAgent: z.string().nullable(),
|
|
17
|
+
});
|
|
18
|
+
|
|
9
19
|
export const detailQuery = defineQueryHandler({
|
|
10
20
|
name: "user-session:detail",
|
|
11
21
|
schema: z.object({ id: z.uuid() }),
|
|
12
22
|
access: { roles: access.admin },
|
|
23
|
+
outputSchema: sessionRowSchema.nullable(),
|
|
13
24
|
handler: async (query, ctx) => {
|
|
14
25
|
const row = await fetchOne<{
|
|
15
26
|
id: string;
|
|
@@ -33,6 +33,20 @@ export const listQuery = definePagedQueryHandler({
|
|
|
33
33
|
sortDirection: z.enum(["asc", "desc"]).optional(),
|
|
34
34
|
}),
|
|
35
35
|
access: { roles: access.admin },
|
|
36
|
+
outputSchema: z.object({
|
|
37
|
+
rows: z.array(
|
|
38
|
+
z.object({
|
|
39
|
+
id: z.string(),
|
|
40
|
+
userId: z.string(),
|
|
41
|
+
createdAt: z.unknown(),
|
|
42
|
+
expiresAt: z.unknown(),
|
|
43
|
+
revokedAt: z.unknown(),
|
|
44
|
+
ip: z.string().nullable(),
|
|
45
|
+
userAgent: z.string().nullable(),
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
48
|
+
nextCursor: z.string().nullable(),
|
|
49
|
+
}),
|
|
36
50
|
handler: async (query, ctx) => {
|
|
37
51
|
const requestedSort = query.payload.sort;
|
|
38
52
|
const sortColumn: SortableColumn =
|