@hasna/contacts 0.6.33 → 0.6.34
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/dist/cli/index.js +45 -11
- package/dist/index.js +3 -0
- package/dist/server/cloud-index.js +45 -11
- package/dist/server/index.js +45 -11
- package/migrations/0013_remote_delete_tombstones.sql +15 -0
- package/migrations/0014_contact_tag_filter_index.sql +9 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/index-0l6aQb1t.css +0 -1
- package/dashboard/dist/assets/index-opnZdkVD.js +0 -229
- package/dashboard/dist/index.html +0 -13
- package/dist/cli/commands/advanced.d.ts +0 -3
- package/dist/cli/commands/advanced.d.ts.map +0 -1
- package/dist/cli/commands/audience.d.ts +0 -18
- package/dist/cli/commands/audience.d.ts.map +0 -1
- package/dist/cli/commands/core.d.ts +0 -3
- package/dist/cli/commands/core.d.ts.map +0 -1
- package/dist/cli/commands/crm.d.ts +0 -3
- package/dist/cli/commands/crm.d.ts.map +0 -1
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/storage.d.ts +0 -3
- package/dist/cli/storage.d.ts.map +0 -1
- package/dist/cli/utils.d.ts +0 -10
- package/dist/cli/utils.d.ts.map +0 -1
- package/dist/cloud/http-storage.d.ts +0 -88
- package/dist/cloud/http-storage.d.ts.map +0 -1
- package/dist/db/activity.d.ts +0 -15
- package/dist/db/activity.d.ts.map +0 -1
- package/dist/db/applications.d.ts +0 -10
- package/dist/db/applications.d.ts.map +0 -1
- package/dist/db/audiences.d.ts +0 -51
- package/dist/db/audiences.d.ts.map +0 -1
- package/dist/db/companies.d.ts +0 -19
- package/dist/db/companies.d.ts.map +0 -1
- package/dist/db/contact-tasks.d.ts +0 -19
- package/dist/db/contact-tasks.d.ts.map +0 -1
- package/dist/db/contacts.d.ts +0 -30
- package/dist/db/contacts.d.ts.map +0 -1
- package/dist/db/coordination.d.ts +0 -30
- package/dist/db/coordination.d.ts.map +0 -1
- package/dist/db/database.d.ts +0 -8
- package/dist/db/database.d.ts.map +0 -1
- package/dist/db/deals.d.ts +0 -14
- package/dist/db/deals.d.ts.map +0 -1
- package/dist/db/documents.d.ts +0 -38
- package/dist/db/documents.d.ts.map +0 -1
- package/dist/db/events.d.ts +0 -14
- package/dist/db/events.d.ts.map +0 -1
- package/dist/db/field-history.d.ts +0 -17
- package/dist/db/field-history.d.ts.map +0 -1
- package/dist/db/freshness.d.ts +0 -24
- package/dist/db/freshness.d.ts.map +0 -1
- package/dist/db/graph.d.ts +0 -21
- package/dist/db/graph.d.ts.map +0 -1
- package/dist/db/groups.d.ts +0 -22
- package/dist/db/groups.d.ts.map +0 -1
- package/dist/db/health.d.ts +0 -40
- package/dist/db/health.d.ts.map +0 -1
- package/dist/db/identity.d.ts +0 -32
- package/dist/db/identity.d.ts.map +0 -1
- package/dist/db/job-history.d.ts +0 -29
- package/dist/db/job-history.d.ts.map +0 -1
- package/dist/db/learnings.d.ts +0 -43
- package/dist/db/learnings.d.ts.map +0 -1
- package/dist/db/notes.d.ts +0 -8
- package/dist/db/notes.d.ts.map +0 -1
- package/dist/db/org-chart.d.ts +0 -37
- package/dist/db/org-chart.d.ts.map +0 -1
- package/dist/db/org-members.d.ts +0 -9
- package/dist/db/org-members.d.ts.map +0 -1
- package/dist/db/paths.d.ts +0 -3
- package/dist/db/paths.d.ts.map +0 -1
- package/dist/db/pg-migrations.d.ts +0 -8
- package/dist/db/pg-migrations.d.ts.map +0 -1
- package/dist/db/relationships.d.ts +0 -30
- package/dist/db/relationships.d.ts.map +0 -1
- package/dist/db/signals.d.ts +0 -18
- package/dist/db/signals.d.ts.map +0 -1
- package/dist/db/sqlite-adapter.d.ts +0 -15
- package/dist/db/sqlite-adapter.d.ts.map +0 -1
- package/dist/db/storage.d.ts +0 -15
- package/dist/db/storage.d.ts.map +0 -1
- package/dist/db/tags.d.ts +0 -14
- package/dist/db/tags.d.ts.map +0 -1
- package/dist/db/tombstones.d.ts +0 -19
- package/dist/db/tombstones.d.ts.map +0 -1
- package/dist/db/vendor-comms.d.ts +0 -15
- package/dist/db/vendor-comms.d.ts.map +0 -1
- package/dist/generated/storage-kit/health.d.ts +0 -20
- package/dist/generated/storage-kit/health.d.ts.map +0 -1
- package/dist/generated/storage-kit/index.d.ts +0 -8
- package/dist/generated/storage-kit/index.d.ts.map +0 -1
- package/dist/generated/storage-kit/migrations.d.ts +0 -48
- package/dist/generated/storage-kit/migrations.d.ts.map +0 -1
- package/dist/generated/storage-kit/mode.d.ts +0 -48
- package/dist/generated/storage-kit/mode.d.ts.map +0 -1
- package/dist/generated/storage-kit/pool.d.ts +0 -34
- package/dist/generated/storage-kit/pool.d.ts.map +0 -1
- package/dist/generated/storage-kit/query.d.ts +0 -36
- package/dist/generated/storage-kit/query.d.ts.map +0 -1
- package/dist/generated/storage-kit/tls.d.ts +0 -26
- package/dist/generated/storage-kit/tls.d.ts.map +0 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/apple-contacts.d.ts +0 -3
- package/dist/lib/apple-contacts.d.ts.map +0 -1
- package/dist/lib/audience-contract.d.ts +0 -215
- package/dist/lib/audience-contract.d.ts.map +0 -1
- package/dist/lib/audit.d.ts +0 -12
- package/dist/lib/audit.d.ts.map +0 -1
- package/dist/lib/brief.d.ts +0 -3
- package/dist/lib/brief.d.ts.map +0 -1
- package/dist/lib/config.d.ts +0 -7
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/connector.d.ts +0 -44
- package/dist/lib/connector.d.ts.map +0 -1
- package/dist/lib/context.d.ts +0 -5
- package/dist/lib/context.d.ts.map +0 -1
- package/dist/lib/dedup.d.ts +0 -10
- package/dist/lib/dedup.d.ts.map +0 -1
- package/dist/lib/document-scanner.d.ts +0 -8
- package/dist/lib/document-scanner.d.ts.map +0 -1
- package/dist/lib/embeddings.d.ts +0 -9
- package/dist/lib/embeddings.d.ts.map +0 -1
- package/dist/lib/export.d.ts +0 -8
- package/dist/lib/export.d.ts.map +0 -1
- package/dist/lib/freshness.d.ts +0 -19
- package/dist/lib/freshness.d.ts.map +0 -1
- package/dist/lib/gmail-import.d.ts +0 -51
- package/dist/lib/gmail-import.d.ts.map +0 -1
- package/dist/lib/google-contacts.d.ts +0 -115
- package/dist/lib/google-contacts.d.ts.map +0 -1
- package/dist/lib/images.d.ts +0 -30
- package/dist/lib/images.d.ts.map +0 -1
- package/dist/lib/import.d.ts +0 -10
- package/dist/lib/import.d.ts.map +0 -1
- package/dist/lib/learning-maintenance.d.ts +0 -8
- package/dist/lib/learning-maintenance.d.ts.map +0 -1
- package/dist/lib/mailery-sync.d.ts +0 -46
- package/dist/lib/mailery-sync.d.ts.map +0 -1
- package/dist/lib/meeting-capture.d.ts +0 -15
- package/dist/lib/meeting-capture.d.ts.map +0 -1
- package/dist/lib/package-version.d.ts +0 -6
- package/dist/lib/package-version.d.ts.map +0 -1
- package/dist/lib/signals.d.ts +0 -9
- package/dist/lib/signals.d.ts.map +0 -1
- package/dist/lib/signature-parser.d.ts +0 -36
- package/dist/lib/signature-parser.d.ts.map +0 -1
- package/dist/lib/stats.d.ts +0 -24
- package/dist/lib/stats.d.ts.map +0 -1
- package/dist/lib/timeline.d.ts +0 -11
- package/dist/lib/timeline.d.ts.map +0 -1
- package/dist/lib/upcoming.d.ts +0 -14
- package/dist/lib/upcoming.d.ts.map +0 -1
- package/dist/lib/vault.d.ts +0 -24
- package/dist/lib/vault.d.ts.map +0 -1
- package/dist/mcp/handlers/advanced.d.ts +0 -14
- package/dist/mcp/handlers/advanced.d.ts.map +0 -1
- package/dist/mcp/handlers/audience.d.ts +0 -9
- package/dist/mcp/handlers/audience.d.ts.map +0 -1
- package/dist/mcp/handlers/core.d.ts +0 -10
- package/dist/mcp/handlers/core.d.ts.map +0 -1
- package/dist/mcp/handlers/crm.d.ts +0 -14
- package/dist/mcp/handlers/crm.d.ts.map +0 -1
- package/dist/mcp/handlers/index.d.ts +0 -4
- package/dist/mcp/handlers/index.d.ts.map +0 -1
- package/dist/mcp/handlers/types.d.ts +0 -10
- package/dist/mcp/handlers/types.d.ts.map +0 -1
- package/dist/mcp/http.d.ts +0 -17
- package/dist/mcp/http.d.ts.map +0 -1
- package/dist/mcp/index.d.ts +0 -5
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/register-tools.d.ts +0 -3
- package/dist/mcp/register-tools.d.ts.map +0 -1
- package/dist/mcp/schema.d.ts +0 -3
- package/dist/mcp/schema.d.ts.map +0 -1
- package/dist/mcp/storage-tools.d.ts +0 -3
- package/dist/mcp/storage-tools.d.ts.map +0 -1
- package/dist/mcp/tools.d.ts +0 -3799
- package/dist/mcp/tools.d.ts.map +0 -1
- package/dist/sdk/index.d.ts +0 -16
- package/dist/sdk/index.d.ts.map +0 -1
- package/dist/sdk/v1.generated.d.ts +0 -203
- package/dist/sdk/v1.generated.d.ts.map +0 -1
- package/dist/server/cloud-index.d.ts +0 -3
- package/dist/server/cloud-index.d.ts.map +0 -1
- package/dist/server/cloud-serve.d.ts +0 -3
- package/dist/server/cloud-serve.d.ts.map +0 -1
- package/dist/server/cloud.d.ts +0 -53
- package/dist/server/cloud.d.ts.map +0 -1
- package/dist/server/index.d.ts +0 -3
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/openapi.d.ts +0 -765
- package/dist/server/openapi.d.ts.map +0 -1
- package/dist/server/pg-store.d.ts +0 -861
- package/dist/server/pg-store.d.ts.map +0 -1
- package/dist/server/security.d.ts +0 -24
- package/dist/server/security.d.ts.map +0 -1
- package/dist/server/serve.d.ts +0 -9
- package/dist/server/serve.d.ts.map +0 -1
- package/dist/server/v1.d.ts +0 -6
- package/dist/server/v1.d.ts.map +0 -1
- package/dist/store/index.d.ts +0 -383
- package/dist/store/index.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -856
- package/dist/types/index.d.ts.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -33708,6 +33708,14 @@ var init_pg_migrations = __esm(() => {
|
|
|
33708
33708
|
CREATE INDEX IF NOT EXISTS idx_contacts_tombstones_deleted_at ON _contacts_tombstones(deleted_at);
|
|
33709
33709
|
|
|
33710
33710
|
INSERT INTO _migrations (version) VALUES (12) ON CONFLICT DO NOTHING;
|
|
33711
|
+
`,
|
|
33712
|
+
`
|
|
33713
|
+
-- The contact_tags primary key is (contact_id, tag_id), which does not
|
|
33714
|
+
-- support the public tag_id filter efficiently. Keep this forward-only and
|
|
33715
|
+
-- idempotent for populated cloud deployments.
|
|
33716
|
+
CREATE INDEX IF NOT EXISTS idx_contact_tags_tag_contact ON contact_tags(tag_id, contact_id);
|
|
33717
|
+
|
|
33718
|
+
INSERT INTO _migrations (version) VALUES (13) ON CONFLICT DO NOTHING;
|
|
33711
33719
|
`
|
|
33712
33720
|
];
|
|
33713
33721
|
});
|
|
@@ -33917,6 +33925,19 @@ class ContactsPgStore {
|
|
|
33917
33925
|
constructor(client) {
|
|
33918
33926
|
this.client = client;
|
|
33919
33927
|
}
|
|
33928
|
+
async attachTags(contacts) {
|
|
33929
|
+
const tagsByContactId = new Map(contacts.map((contact) => [contact.id, []]));
|
|
33930
|
+
if (contacts.length === 0)
|
|
33931
|
+
return [];
|
|
33932
|
+
const rows = await this.client.many(`SELECT ct.contact_id, t.*
|
|
33933
|
+
FROM contact_tags ct
|
|
33934
|
+
JOIN tags t ON t.id = ct.tag_id
|
|
33935
|
+
WHERE ct.contact_id = ANY($1::text[])
|
|
33936
|
+
ORDER BY t.name ASC`, [contacts.map((contact) => contact.id)]);
|
|
33937
|
+
for (const row of rows)
|
|
33938
|
+
tagsByContactId.get(row.contact_id)?.push(mapTag(row));
|
|
33939
|
+
return contacts.map((contact) => ({ ...contact, tags: tagsByContactId.get(contact.id) ?? [] }));
|
|
33940
|
+
}
|
|
33920
33941
|
async listContacts(filter = {}) {
|
|
33921
33942
|
const limit = Math.min(Math.max(filter.limit ?? 50, 1), 500);
|
|
33922
33943
|
const offset = Math.max(filter.offset ?? 0, 0);
|
|
@@ -33930,6 +33951,10 @@ class ContactsPgStore {
|
|
|
33930
33951
|
params.push(filter.status);
|
|
33931
33952
|
where.push(`status = $${params.length}`);
|
|
33932
33953
|
}
|
|
33954
|
+
if (filter.tag_id) {
|
|
33955
|
+
params.push(filter.tag_id);
|
|
33956
|
+
where.push(`EXISTS (SELECT 1 FROM contact_tags ct WHERE ct.contact_id = contacts.id AND ct.tag_id = $${params.length})`);
|
|
33957
|
+
}
|
|
33933
33958
|
if (filter.q) {
|
|
33934
33959
|
params.push(filter.q);
|
|
33935
33960
|
where.push(`search_vector @@ plainto_tsquery('simple', $${params.length})`);
|
|
@@ -33938,11 +33963,13 @@ class ContactsPgStore {
|
|
|
33938
33963
|
const countRow = await this.client.get(`SELECT COUNT(*)::text AS count FROM contacts ${whereSql}`, params);
|
|
33939
33964
|
params.push(limit, offset);
|
|
33940
33965
|
const rows = await this.client.many(`SELECT * FROM contacts ${whereSql} ORDER BY display_name ASC LIMIT $${params.length - 1} OFFSET $${params.length}`, params);
|
|
33941
|
-
return { contacts: rows.map(mapContact), count: Number(countRow?.count ?? rows.length) };
|
|
33966
|
+
return { contacts: await this.attachTags(rows.map(mapContact)), count: Number(countRow?.count ?? rows.length) };
|
|
33942
33967
|
}
|
|
33943
33968
|
async getContact(id) {
|
|
33944
33969
|
const row = await this.client.get(`SELECT * FROM contacts WHERE id = $1`, [id]);
|
|
33945
|
-
|
|
33970
|
+
if (!row)
|
|
33971
|
+
return null;
|
|
33972
|
+
return (await this.attachTags([mapContact(row)]))[0];
|
|
33946
33973
|
}
|
|
33947
33974
|
async createContact(input) {
|
|
33948
33975
|
const id = uuid3();
|
|
@@ -33978,7 +34005,7 @@ class ContactsPgStore {
|
|
|
33978
34005
|
input.priority ?? 3,
|
|
33979
34006
|
input.timezone ?? null
|
|
33980
34007
|
]);
|
|
33981
|
-
return mapContact(row);
|
|
34008
|
+
return (await this.attachTags([mapContact(row)]))[0];
|
|
33982
34009
|
}
|
|
33983
34010
|
async updateContact(id, input) {
|
|
33984
34011
|
const allowed = {};
|
|
@@ -34018,7 +34045,7 @@ class ContactsPgStore {
|
|
|
34018
34045
|
const sets = keys.map((k, i) => `${k} = $${i + 2}`);
|
|
34019
34046
|
sets.push(`updated_at = NOW()`);
|
|
34020
34047
|
const row = await this.client.get(`UPDATE contacts SET ${sets.join(", ")} WHERE id = $1 RETURNING *`, [id, ...keys.map((k) => allowed[k])]);
|
|
34021
|
-
return row ? mapContact(row) : null;
|
|
34048
|
+
return row ? (await this.attachTags([mapContact(row)]))[0] : null;
|
|
34022
34049
|
}
|
|
34023
34050
|
async deleteContact(id) {
|
|
34024
34051
|
const result = await this.client.query(`DELETE FROM contacts WHERE id = $1`, [id]);
|
|
@@ -35479,6 +35506,16 @@ async function readJson(req) {
|
|
|
35479
35506
|
return null;
|
|
35480
35507
|
}
|
|
35481
35508
|
}
|
|
35509
|
+
function contactListFilterFromUrl(url) {
|
|
35510
|
+
return {
|
|
35511
|
+
...url.searchParams.get("company_id") ? { company_id: url.searchParams.get("company_id") } : {},
|
|
35512
|
+
...url.searchParams.get("status") ? { status: url.searchParams.get("status") } : {},
|
|
35513
|
+
...url.searchParams.get("tag_id") ? { tag_id: url.searchParams.get("tag_id") } : {},
|
|
35514
|
+
...url.searchParams.get("q") ? { q: url.searchParams.get("q") } : {},
|
|
35515
|
+
...url.searchParams.get("limit") ? { limit: Number(url.searchParams.get("limit")) } : {},
|
|
35516
|
+
...url.searchParams.get("offset") ? { offset: Number(url.searchParams.get("offset")) } : {}
|
|
35517
|
+
};
|
|
35518
|
+
}
|
|
35482
35519
|
async function handleV1Request(req, url) {
|
|
35483
35520
|
const path = url.pathname;
|
|
35484
35521
|
if (path !== "/v1" && !path.startsWith("/v1/"))
|
|
@@ -35592,13 +35629,7 @@ async function handleV1Request(req, url) {
|
|
|
35592
35629
|
if (resource === "contacts") {
|
|
35593
35630
|
if (!id) {
|
|
35594
35631
|
if (method === "GET") {
|
|
35595
|
-
const result = await store.listContacts(
|
|
35596
|
-
...url.searchParams.get("company_id") ? { company_id: url.searchParams.get("company_id") } : {},
|
|
35597
|
-
...url.searchParams.get("status") ? { status: url.searchParams.get("status") } : {},
|
|
35598
|
-
...url.searchParams.get("q") ? { q: url.searchParams.get("q") } : {},
|
|
35599
|
-
...url.searchParams.get("limit") ? { limit: Number(url.searchParams.get("limit")) } : {},
|
|
35600
|
-
...url.searchParams.get("offset") ? { offset: Number(url.searchParams.get("offset")) } : {}
|
|
35601
|
-
});
|
|
35632
|
+
const result = await store.listContacts(contactListFilterFromUrl(url));
|
|
35602
35633
|
return json5(result);
|
|
35603
35634
|
}
|
|
35604
35635
|
if (method === "POST") {
|
|
@@ -36365,6 +36396,7 @@ function buildV1OpenApiDocument(version2 = getPackageVersion()) {
|
|
|
36365
36396
|
{ name: "q", in: "query", schema: { type: "string" } },
|
|
36366
36397
|
{ name: "company_id", in: "query", schema: { type: "string" } },
|
|
36367
36398
|
{ name: "status", in: "query", schema: { type: "string" } },
|
|
36399
|
+
{ name: "tag_id", in: "query", schema: { type: "string" } },
|
|
36368
36400
|
{ name: "limit", in: "query", schema: { type: "number" } },
|
|
36369
36401
|
{ name: "offset", in: "query", schema: { type: "number" } }
|
|
36370
36402
|
],
|
|
@@ -36570,6 +36602,7 @@ var init_openapi = __esm(() => {
|
|
|
36570
36602
|
init_package_version();
|
|
36571
36603
|
contactSchema = {
|
|
36572
36604
|
type: "object",
|
|
36605
|
+
required: ["tags"],
|
|
36573
36606
|
properties: {
|
|
36574
36607
|
id: { type: "string" },
|
|
36575
36608
|
first_name: { type: "string" },
|
|
@@ -36584,6 +36617,7 @@ var init_openapi = __esm(() => {
|
|
|
36584
36617
|
sensitivity: { type: "string" },
|
|
36585
36618
|
archived: { type: "boolean" },
|
|
36586
36619
|
priority: { type: "number" },
|
|
36620
|
+
tags: { type: "array", items: { $ref: "#/components/schemas/Tag" } },
|
|
36587
36621
|
created_at: { type: "string" },
|
|
36588
36622
|
updated_at: { type: "string" }
|
|
36589
36623
|
}
|
package/dist/index.js
CHANGED
|
@@ -6123,6 +6123,7 @@ function getPackageVersion() {
|
|
|
6123
6123
|
// src/server/openapi.ts
|
|
6124
6124
|
var contactSchema = {
|
|
6125
6125
|
type: "object",
|
|
6126
|
+
required: ["tags"],
|
|
6126
6127
|
properties: {
|
|
6127
6128
|
id: { type: "string" },
|
|
6128
6129
|
first_name: { type: "string" },
|
|
@@ -6137,6 +6138,7 @@ var contactSchema = {
|
|
|
6137
6138
|
sensitivity: { type: "string" },
|
|
6138
6139
|
archived: { type: "boolean" },
|
|
6139
6140
|
priority: { type: "number" },
|
|
6141
|
+
tags: { type: "array", items: { $ref: "#/components/schemas/Tag" } },
|
|
6140
6142
|
created_at: { type: "string" },
|
|
6141
6143
|
updated_at: { type: "string" }
|
|
6142
6144
|
}
|
|
@@ -6269,6 +6271,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
6269
6271
|
{ name: "q", in: "query", schema: { type: "string" } },
|
|
6270
6272
|
{ name: "company_id", in: "query", schema: { type: "string" } },
|
|
6271
6273
|
{ name: "status", in: "query", schema: { type: "string" } },
|
|
6274
|
+
{ name: "tag_id", in: "query", schema: { type: "string" } },
|
|
6272
6275
|
{ name: "limit", in: "query", schema: { type: "number" } },
|
|
6273
6276
|
{ name: "offset", in: "query", schema: { type: "number" } }
|
|
6274
6277
|
],
|
|
@@ -6170,6 +6170,14 @@ var PG_MIGRATIONS = [
|
|
|
6170
6170
|
CREATE INDEX IF NOT EXISTS idx_contacts_tombstones_deleted_at ON _contacts_tombstones(deleted_at);
|
|
6171
6171
|
|
|
6172
6172
|
INSERT INTO _migrations (version) VALUES (12) ON CONFLICT DO NOTHING;
|
|
6173
|
+
`,
|
|
6174
|
+
`
|
|
6175
|
+
-- The contact_tags primary key is (contact_id, tag_id), which does not
|
|
6176
|
+
-- support the public tag_id filter efficiently. Keep this forward-only and
|
|
6177
|
+
-- idempotent for populated cloud deployments.
|
|
6178
|
+
CREATE INDEX IF NOT EXISTS idx_contact_tags_tag_contact ON contact_tags(tag_id, contact_id);
|
|
6179
|
+
|
|
6180
|
+
INSERT INTO _migrations (version) VALUES (13) ON CONFLICT DO NOTHING;
|
|
6173
6181
|
`
|
|
6174
6182
|
];
|
|
6175
6183
|
|
|
@@ -6281,6 +6289,7 @@ async function closeCloud() {
|
|
|
6281
6289
|
// src/server/openapi.ts
|
|
6282
6290
|
var contactSchema = {
|
|
6283
6291
|
type: "object",
|
|
6292
|
+
required: ["tags"],
|
|
6284
6293
|
properties: {
|
|
6285
6294
|
id: { type: "string" },
|
|
6286
6295
|
first_name: { type: "string" },
|
|
@@ -6295,6 +6304,7 @@ var contactSchema = {
|
|
|
6295
6304
|
sensitivity: { type: "string" },
|
|
6296
6305
|
archived: { type: "boolean" },
|
|
6297
6306
|
priority: { type: "number" },
|
|
6307
|
+
tags: { type: "array", items: { $ref: "#/components/schemas/Tag" } },
|
|
6298
6308
|
created_at: { type: "string" },
|
|
6299
6309
|
updated_at: { type: "string" }
|
|
6300
6310
|
}
|
|
@@ -6427,6 +6437,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
6427
6437
|
{ name: "q", in: "query", schema: { type: "string" } },
|
|
6428
6438
|
{ name: "company_id", in: "query", schema: { type: "string" } },
|
|
6429
6439
|
{ name: "status", in: "query", schema: { type: "string" } },
|
|
6440
|
+
{ name: "tag_id", in: "query", schema: { type: "string" } },
|
|
6430
6441
|
{ name: "limit", in: "query", schema: { type: "number" } },
|
|
6431
6442
|
{ name: "offset", in: "query", schema: { type: "number" } }
|
|
6432
6443
|
],
|
|
@@ -6736,6 +6747,19 @@ class ContactsPgStore {
|
|
|
6736
6747
|
constructor(client) {
|
|
6737
6748
|
this.client = client;
|
|
6738
6749
|
}
|
|
6750
|
+
async attachTags(contacts) {
|
|
6751
|
+
const tagsByContactId = new Map(contacts.map((contact) => [contact.id, []]));
|
|
6752
|
+
if (contacts.length === 0)
|
|
6753
|
+
return [];
|
|
6754
|
+
const rows = await this.client.many(`SELECT ct.contact_id, t.*
|
|
6755
|
+
FROM contact_tags ct
|
|
6756
|
+
JOIN tags t ON t.id = ct.tag_id
|
|
6757
|
+
WHERE ct.contact_id = ANY($1::text[])
|
|
6758
|
+
ORDER BY t.name ASC`, [contacts.map((contact) => contact.id)]);
|
|
6759
|
+
for (const row of rows)
|
|
6760
|
+
tagsByContactId.get(row.contact_id)?.push(mapTag(row));
|
|
6761
|
+
return contacts.map((contact) => ({ ...contact, tags: tagsByContactId.get(contact.id) ?? [] }));
|
|
6762
|
+
}
|
|
6739
6763
|
async listContacts(filter = {}) {
|
|
6740
6764
|
const limit = Math.min(Math.max(filter.limit ?? 50, 1), 500);
|
|
6741
6765
|
const offset = Math.max(filter.offset ?? 0, 0);
|
|
@@ -6749,6 +6773,10 @@ class ContactsPgStore {
|
|
|
6749
6773
|
params.push(filter.status);
|
|
6750
6774
|
where.push(`status = $${params.length}`);
|
|
6751
6775
|
}
|
|
6776
|
+
if (filter.tag_id) {
|
|
6777
|
+
params.push(filter.tag_id);
|
|
6778
|
+
where.push(`EXISTS (SELECT 1 FROM contact_tags ct WHERE ct.contact_id = contacts.id AND ct.tag_id = $${params.length})`);
|
|
6779
|
+
}
|
|
6752
6780
|
if (filter.q) {
|
|
6753
6781
|
params.push(filter.q);
|
|
6754
6782
|
where.push(`search_vector @@ plainto_tsquery('simple', $${params.length})`);
|
|
@@ -6757,11 +6785,13 @@ class ContactsPgStore {
|
|
|
6757
6785
|
const countRow = await this.client.get(`SELECT COUNT(*)::text AS count FROM contacts ${whereSql}`, params);
|
|
6758
6786
|
params.push(limit, offset);
|
|
6759
6787
|
const rows = await this.client.many(`SELECT * FROM contacts ${whereSql} ORDER BY display_name ASC LIMIT $${params.length - 1} OFFSET $${params.length}`, params);
|
|
6760
|
-
return { contacts: rows.map(mapContact), count: Number(countRow?.count ?? rows.length) };
|
|
6788
|
+
return { contacts: await this.attachTags(rows.map(mapContact)), count: Number(countRow?.count ?? rows.length) };
|
|
6761
6789
|
}
|
|
6762
6790
|
async getContact(id) {
|
|
6763
6791
|
const row = await this.client.get(`SELECT * FROM contacts WHERE id = $1`, [id]);
|
|
6764
|
-
|
|
6792
|
+
if (!row)
|
|
6793
|
+
return null;
|
|
6794
|
+
return (await this.attachTags([mapContact(row)]))[0];
|
|
6765
6795
|
}
|
|
6766
6796
|
async createContact(input) {
|
|
6767
6797
|
const id = uuid();
|
|
@@ -6797,7 +6827,7 @@ class ContactsPgStore {
|
|
|
6797
6827
|
input.priority ?? 3,
|
|
6798
6828
|
input.timezone ?? null
|
|
6799
6829
|
]);
|
|
6800
|
-
return mapContact(row);
|
|
6830
|
+
return (await this.attachTags([mapContact(row)]))[0];
|
|
6801
6831
|
}
|
|
6802
6832
|
async updateContact(id, input) {
|
|
6803
6833
|
const allowed = {};
|
|
@@ -6837,7 +6867,7 @@ class ContactsPgStore {
|
|
|
6837
6867
|
const sets = keys.map((k, i) => `${k} = $${i + 2}`);
|
|
6838
6868
|
sets.push(`updated_at = NOW()`);
|
|
6839
6869
|
const row = await this.client.get(`UPDATE contacts SET ${sets.join(", ")} WHERE id = $1 RETURNING *`, [id, ...keys.map((k) => allowed[k])]);
|
|
6840
|
-
return row ? mapContact(row) : null;
|
|
6870
|
+
return row ? (await this.attachTags([mapContact(row)]))[0] : null;
|
|
6841
6871
|
}
|
|
6842
6872
|
async deleteContact(id) {
|
|
6843
6873
|
const result = await this.client.query(`DELETE FROM contacts WHERE id = $1`, [id]);
|
|
@@ -8299,6 +8329,16 @@ async function readJson(req) {
|
|
|
8299
8329
|
return null;
|
|
8300
8330
|
}
|
|
8301
8331
|
}
|
|
8332
|
+
function contactListFilterFromUrl(url) {
|
|
8333
|
+
return {
|
|
8334
|
+
...url.searchParams.get("company_id") ? { company_id: url.searchParams.get("company_id") } : {},
|
|
8335
|
+
...url.searchParams.get("status") ? { status: url.searchParams.get("status") } : {},
|
|
8336
|
+
...url.searchParams.get("tag_id") ? { tag_id: url.searchParams.get("tag_id") } : {},
|
|
8337
|
+
...url.searchParams.get("q") ? { q: url.searchParams.get("q") } : {},
|
|
8338
|
+
...url.searchParams.get("limit") ? { limit: Number(url.searchParams.get("limit")) } : {},
|
|
8339
|
+
...url.searchParams.get("offset") ? { offset: Number(url.searchParams.get("offset")) } : {}
|
|
8340
|
+
};
|
|
8341
|
+
}
|
|
8302
8342
|
async function handleV1Request(req, url) {
|
|
8303
8343
|
const path = url.pathname;
|
|
8304
8344
|
if (path !== "/v1" && !path.startsWith("/v1/"))
|
|
@@ -8412,13 +8452,7 @@ async function handleV1Request(req, url) {
|
|
|
8412
8452
|
if (resource === "contacts") {
|
|
8413
8453
|
if (!id) {
|
|
8414
8454
|
if (method === "GET") {
|
|
8415
|
-
const result = await store.listContacts(
|
|
8416
|
-
...url.searchParams.get("company_id") ? { company_id: url.searchParams.get("company_id") } : {},
|
|
8417
|
-
...url.searchParams.get("status") ? { status: url.searchParams.get("status") } : {},
|
|
8418
|
-
...url.searchParams.get("q") ? { q: url.searchParams.get("q") } : {},
|
|
8419
|
-
...url.searchParams.get("limit") ? { limit: Number(url.searchParams.get("limit")) } : {},
|
|
8420
|
-
...url.searchParams.get("offset") ? { offset: Number(url.searchParams.get("offset")) } : {}
|
|
8421
|
-
});
|
|
8455
|
+
const result = await store.listContacts(contactListFilterFromUrl(url));
|
|
8422
8456
|
return json(result);
|
|
8423
8457
|
}
|
|
8424
8458
|
if (method === "POST") {
|
package/dist/server/index.js
CHANGED
|
@@ -9016,6 +9016,14 @@ var init_pg_migrations = __esm(() => {
|
|
|
9016
9016
|
CREATE INDEX IF NOT EXISTS idx_contacts_tombstones_deleted_at ON _contacts_tombstones(deleted_at);
|
|
9017
9017
|
|
|
9018
9018
|
INSERT INTO _migrations (version) VALUES (12) ON CONFLICT DO NOTHING;
|
|
9019
|
+
`,
|
|
9020
|
+
`
|
|
9021
|
+
-- The contact_tags primary key is (contact_id, tag_id), which does not
|
|
9022
|
+
-- support the public tag_id filter efficiently. Keep this forward-only and
|
|
9023
|
+
-- idempotent for populated cloud deployments.
|
|
9024
|
+
CREATE INDEX IF NOT EXISTS idx_contact_tags_tag_contact ON contact_tags(tag_id, contact_id);
|
|
9025
|
+
|
|
9026
|
+
INSERT INTO _migrations (version) VALUES (13) ON CONFLICT DO NOTHING;
|
|
9019
9027
|
`
|
|
9020
9028
|
];
|
|
9021
9029
|
});
|
|
@@ -31351,6 +31359,19 @@ class ContactsPgStore {
|
|
|
31351
31359
|
constructor(client) {
|
|
31352
31360
|
this.client = client;
|
|
31353
31361
|
}
|
|
31362
|
+
async attachTags(contacts) {
|
|
31363
|
+
const tagsByContactId = new Map(contacts.map((contact) => [contact.id, []]));
|
|
31364
|
+
if (contacts.length === 0)
|
|
31365
|
+
return [];
|
|
31366
|
+
const rows = await this.client.many(`SELECT ct.contact_id, t.*
|
|
31367
|
+
FROM contact_tags ct
|
|
31368
|
+
JOIN tags t ON t.id = ct.tag_id
|
|
31369
|
+
WHERE ct.contact_id = ANY($1::text[])
|
|
31370
|
+
ORDER BY t.name ASC`, [contacts.map((contact) => contact.id)]);
|
|
31371
|
+
for (const row of rows)
|
|
31372
|
+
tagsByContactId.get(row.contact_id)?.push(mapTag(row));
|
|
31373
|
+
return contacts.map((contact) => ({ ...contact, tags: tagsByContactId.get(contact.id) ?? [] }));
|
|
31374
|
+
}
|
|
31354
31375
|
async listContacts(filter = {}) {
|
|
31355
31376
|
const limit = Math.min(Math.max(filter.limit ?? 50, 1), 500);
|
|
31356
31377
|
const offset = Math.max(filter.offset ?? 0, 0);
|
|
@@ -31364,6 +31385,10 @@ class ContactsPgStore {
|
|
|
31364
31385
|
params.push(filter.status);
|
|
31365
31386
|
where.push(`status = $${params.length}`);
|
|
31366
31387
|
}
|
|
31388
|
+
if (filter.tag_id) {
|
|
31389
|
+
params.push(filter.tag_id);
|
|
31390
|
+
where.push(`EXISTS (SELECT 1 FROM contact_tags ct WHERE ct.contact_id = contacts.id AND ct.tag_id = $${params.length})`);
|
|
31391
|
+
}
|
|
31367
31392
|
if (filter.q) {
|
|
31368
31393
|
params.push(filter.q);
|
|
31369
31394
|
where.push(`search_vector @@ plainto_tsquery('simple', $${params.length})`);
|
|
@@ -31372,11 +31397,13 @@ class ContactsPgStore {
|
|
|
31372
31397
|
const countRow = await this.client.get(`SELECT COUNT(*)::text AS count FROM contacts ${whereSql}`, params);
|
|
31373
31398
|
params.push(limit, offset);
|
|
31374
31399
|
const rows = await this.client.many(`SELECT * FROM contacts ${whereSql} ORDER BY display_name ASC LIMIT $${params.length - 1} OFFSET $${params.length}`, params);
|
|
31375
|
-
return { contacts: rows.map(mapContact), count: Number(countRow?.count ?? rows.length) };
|
|
31400
|
+
return { contacts: await this.attachTags(rows.map(mapContact)), count: Number(countRow?.count ?? rows.length) };
|
|
31376
31401
|
}
|
|
31377
31402
|
async getContact(id) {
|
|
31378
31403
|
const row = await this.client.get(`SELECT * FROM contacts WHERE id = $1`, [id]);
|
|
31379
|
-
|
|
31404
|
+
if (!row)
|
|
31405
|
+
return null;
|
|
31406
|
+
return (await this.attachTags([mapContact(row)]))[0];
|
|
31380
31407
|
}
|
|
31381
31408
|
async createContact(input) {
|
|
31382
31409
|
const id = uuid3();
|
|
@@ -31412,7 +31439,7 @@ class ContactsPgStore {
|
|
|
31412
31439
|
input.priority ?? 3,
|
|
31413
31440
|
input.timezone ?? null
|
|
31414
31441
|
]);
|
|
31415
|
-
return mapContact(row);
|
|
31442
|
+
return (await this.attachTags([mapContact(row)]))[0];
|
|
31416
31443
|
}
|
|
31417
31444
|
async updateContact(id, input) {
|
|
31418
31445
|
const allowed = {};
|
|
@@ -31452,7 +31479,7 @@ class ContactsPgStore {
|
|
|
31452
31479
|
const sets = keys.map((k, i) => `${k} = $${i + 2}`);
|
|
31453
31480
|
sets.push(`updated_at = NOW()`);
|
|
31454
31481
|
const row = await this.client.get(`UPDATE contacts SET ${sets.join(", ")} WHERE id = $1 RETURNING *`, [id, ...keys.map((k) => allowed[k])]);
|
|
31455
|
-
return row ? mapContact(row) : null;
|
|
31482
|
+
return row ? (await this.attachTags([mapContact(row)]))[0] : null;
|
|
31456
31483
|
}
|
|
31457
31484
|
async deleteContact(id) {
|
|
31458
31485
|
const result = await this.client.query(`DELETE FROM contacts WHERE id = $1`, [id]);
|
|
@@ -32914,6 +32941,16 @@ async function readJson(req) {
|
|
|
32914
32941
|
return null;
|
|
32915
32942
|
}
|
|
32916
32943
|
}
|
|
32944
|
+
function contactListFilterFromUrl(url) {
|
|
32945
|
+
return {
|
|
32946
|
+
...url.searchParams.get("company_id") ? { company_id: url.searchParams.get("company_id") } : {},
|
|
32947
|
+
...url.searchParams.get("status") ? { status: url.searchParams.get("status") } : {},
|
|
32948
|
+
...url.searchParams.get("tag_id") ? { tag_id: url.searchParams.get("tag_id") } : {},
|
|
32949
|
+
...url.searchParams.get("q") ? { q: url.searchParams.get("q") } : {},
|
|
32950
|
+
...url.searchParams.get("limit") ? { limit: Number(url.searchParams.get("limit")) } : {},
|
|
32951
|
+
...url.searchParams.get("offset") ? { offset: Number(url.searchParams.get("offset")) } : {}
|
|
32952
|
+
};
|
|
32953
|
+
}
|
|
32917
32954
|
async function handleV1Request(req, url) {
|
|
32918
32955
|
const path = url.pathname;
|
|
32919
32956
|
if (path !== "/v1" && !path.startsWith("/v1/"))
|
|
@@ -33027,13 +33064,7 @@ async function handleV1Request(req, url) {
|
|
|
33027
33064
|
if (resource === "contacts") {
|
|
33028
33065
|
if (!id) {
|
|
33029
33066
|
if (method === "GET") {
|
|
33030
|
-
const result = await store.listContacts(
|
|
33031
|
-
...url.searchParams.get("company_id") ? { company_id: url.searchParams.get("company_id") } : {},
|
|
33032
|
-
...url.searchParams.get("status") ? { status: url.searchParams.get("status") } : {},
|
|
33033
|
-
...url.searchParams.get("q") ? { q: url.searchParams.get("q") } : {},
|
|
33034
|
-
...url.searchParams.get("limit") ? { limit: Number(url.searchParams.get("limit")) } : {},
|
|
33035
|
-
...url.searchParams.get("offset") ? { offset: Number(url.searchParams.get("offset")) } : {}
|
|
33036
|
-
});
|
|
33067
|
+
const result = await store.listContacts(contactListFilterFromUrl(url));
|
|
33037
33068
|
return json5(result);
|
|
33038
33069
|
}
|
|
33039
33070
|
if (method === "POST") {
|
|
@@ -33693,6 +33724,7 @@ function getPackageVersion() {
|
|
|
33693
33724
|
// src/server/openapi.ts
|
|
33694
33725
|
var contactSchema = {
|
|
33695
33726
|
type: "object",
|
|
33727
|
+
required: ["tags"],
|
|
33696
33728
|
properties: {
|
|
33697
33729
|
id: { type: "string" },
|
|
33698
33730
|
first_name: { type: "string" },
|
|
@@ -33707,6 +33739,7 @@ var contactSchema = {
|
|
|
33707
33739
|
sensitivity: { type: "string" },
|
|
33708
33740
|
archived: { type: "boolean" },
|
|
33709
33741
|
priority: { type: "number" },
|
|
33742
|
+
tags: { type: "array", items: { $ref: "#/components/schemas/Tag" } },
|
|
33710
33743
|
created_at: { type: "string" },
|
|
33711
33744
|
updated_at: { type: "string" }
|
|
33712
33745
|
}
|
|
@@ -33839,6 +33872,7 @@ function buildV1OpenApiDocument(version2 = getPackageVersion()) {
|
|
|
33839
33872
|
{ name: "q", in: "query", schema: { type: "string" } },
|
|
33840
33873
|
{ name: "company_id", in: "query", schema: { type: "string" } },
|
|
33841
33874
|
{ name: "status", in: "query", schema: { type: "string" } },
|
|
33875
|
+
{ name: "tag_id", in: "query", schema: { type: "string" } },
|
|
33842
33876
|
{ name: "limit", in: "query", schema: { type: "number" } },
|
|
33843
33877
|
{ name: "offset", in: "query", schema: { type: "number" } }
|
|
33844
33878
|
],
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- @hasna/contacts cloud schema (A1 pure-remote) — generated from src/db/pg-migrations.ts
|
|
2
|
+
-- Idempotent and forward-only: retains deletion metadata for remote sync.
|
|
3
|
+
|
|
4
|
+
CREATE TABLE IF NOT EXISTS _contacts_tombstones (
|
|
5
|
+
table_name TEXT NOT NULL CHECK(table_name IN ('contacts','companies','tags')),
|
|
6
|
+
row_id TEXT NOT NULL,
|
|
7
|
+
deleted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
8
|
+
actor TEXT,
|
|
9
|
+
reason TEXT,
|
|
10
|
+
PRIMARY KEY (table_name, row_id)
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
CREATE INDEX IF NOT EXISTS idx_contacts_tombstones_deleted_at ON _contacts_tombstones(deleted_at);
|
|
14
|
+
|
|
15
|
+
INSERT INTO _migrations (version) VALUES (12) ON CONFLICT DO NOTHING;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- @hasna/contacts cloud schema (A1 pure-remote) — generated from src/db/pg-migrations.ts
|
|
2
|
+
-- Idempotent and forward-only: supports GET /v1/contacts?tag_id=... filters.
|
|
3
|
+
|
|
4
|
+
-- The contact_tags primary key is (contact_id, tag_id), which does not
|
|
5
|
+
-- support the public tag_id filter efficiently. Keep this forward-only and
|
|
6
|
+
-- idempotent for populated cloud deployments.
|
|
7
|
+
CREATE INDEX IF NOT EXISTS idx_contact_tags_tag_contact ON contact_tags(tag_id, contact_id);
|
|
8
|
+
|
|
9
|
+
INSERT INTO _migrations (version) VALUES (13) ON CONFLICT DO NOTHING;
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-duration:initial;--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-200:oklch(88.5% .062 18.334);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-950:oklch(28.6% .066 53.813);--color-green-50:oklch(98.2% .018 155.826);--color-green-200:oklch(92.5% .084 155.995);--color-green-800:oklch(44.8% .119 151.328);--color-green-900:oklch(39.3% .095 152.535);--color-green-950:oklch(26.6% .065 152.934);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--animate-spin:spin 1s linear infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:root{--background:0 0% 100%;--foreground:222.2 84% 4.9%;--card:0 0% 100%;--card-foreground:222.2 84% 4.9%;--popover:0 0% 100%;--popover-foreground:222.2 84% 4.9%;--primary:221.2 83.2% 53.3%;--primary-foreground:210 40% 98%;--secondary:210 40% 96.1%;--secondary-foreground:222.2 47.4% 11.2%;--muted:210 40% 96.1%;--muted-foreground:215.4 16.3% 46.9%;--accent:210 40% 96.1%;--accent-foreground:222.2 47.4% 11.2%;--destructive:0 84.2% 60.2%;--destructive-foreground:210 40% 98%;--border:214.3 31.8% 91.4%;--input:214.3 31.8% 91.4%;--ring:221.2 83.2% 53.3%;--radius:.5rem}.dark{--background:222.2 84% 4.9%;--foreground:210 40% 98%;--card:222.2 84% 4.9%;--card-foreground:210 40% 98%;--popover:222.2 84% 4.9%;--popover-foreground:210 40% 98%;--primary:217.2 91.2% 59.8%;--primary-foreground:222.2 47.4% 11.2%;--secondary:217.2 32.6% 17.5%;--secondary-foreground:210 40% 98%;--muted:217.2 32.6% 17.5%;--muted-foreground:215 20.2% 65.1%;--accent:217.2 32.6% 17.5%;--accent-foreground:210 40% 98%;--destructive:0 62.8% 30.6%;--destructive-foreground:210 40% 98%;--border:217.2 32.6% 17.5%;--input:217.2 32.6% 17.5%;--ring:224.3 76.3% 48%}*{border-color:hsl(var(--border))}body{background-color:hsl(var(--background));color:hsl(var(--foreground));min-height:100vh;margin:0;font-family:system-ui,-apple-system,sans-serif}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing) * 0)}.end{inset-inline-end:var(--spacing)}.top-4{top:calc(var(--spacing) * 4)}.top-\[50\%\]{top:50%}.right-1{right:calc(var(--spacing) * 1)}.right-2{right:calc(var(--spacing) * 2)}.right-4{right:calc(var(--spacing) * 4)}.right-6{right:calc(var(--spacing) * 6)}.bottom-6{bottom:calc(var(--spacing) * 6)}.left-2{left:calc(var(--spacing) * 2)}.left-3{left:calc(var(--spacing) * 3)}.left-\[50\%\]{left:50%}.z-50{z-index:50}.-mx-1{margin-inline:calc(var(--spacing) * -1)}.mx-auto{margin-inline:auto}.my-1{margin-block:calc(var(--spacing) * 1)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mr-1{margin-right:calc(var(--spacing) * 1)}.mr-2{margin-right:calc(var(--spacing) * 2)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-2{margin-left:calc(var(--spacing) * 2)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.size-3{width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.size-16{width:calc(var(--spacing) * 16);height:calc(var(--spacing) * 16)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-14{height:calc(var(--spacing) * 14)}.h-\[calc\(100vh-56px\)\]{height:calc(100vh - 56px)}.h-\[var\(--radix-select-trigger-height\)\]{height:var(--radix-select-trigger-height)}.h-full{height:100%}.h-px{height:1px}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[90vh\]{max-height:90vh}.min-h-screen{min-height:100vh}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-9{width:calc(var(--spacing) * 9)}.w-80{width:calc(var(--spacing) * 80)}.w-full{width:100%}.max-w-7xl{max-width:var(--container-7xl)}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[8rem\]{min-width:8rem}.min-w-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.caption-bottom{caption-side:bottom}.translate-x-\[-50\%\]{--tw-translate-x:-50%;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-1{--tw-translate-y:calc(var(--spacing) * 1);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-\[-50\%\]{--tw-translate-y:-50%;translate:var(--tw-translate-x) var(--tw-translate-y)}.scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.scale-110{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x) var(--tw-scale-y)}.rotate-0{rotate:0deg}.rotate-90{rotate:90deg}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing) * 1)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 8) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-green-200{border-color:var(--color-green-200)}.border-red-200{border-color:var(--color-red-200)}.border-transparent{border-color:#0000}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab,red,red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black) 50%,transparent)}}.bg-green-50{background-color:var(--color-green-50)}.bg-red-50{background-color:var(--color-red-50)}.bg-transparent{background-color:#0000}.bg-yellow-50{background-color:var(--color-yellow-50)}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-0{padding-block:calc(var(--spacing) * 0)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.pt-0{padding-top:calc(var(--spacing) * 0)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pr-9{padding-right:calc(var(--spacing) * 9)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-6{padding-left:calc(var(--spacing) * 6)}.pl-8{padding-left:calc(var(--spacing) * 8)}.pl-9{padding-left:calc(var(--spacing) * 9)}.text-center{text-align:center}.text-left{text-align:left}.align-middle{vertical-align:middle}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.leading-none{--tw-leading:1;line-height:1}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-green-800{color:var(--color-green-800)}.text-red-800{color:var(--color-red-800)}.text-white{color:var(--color-white)}.text-yellow-800{color:var(--color-yellow-800)}.capitalize{text-transform:capitalize}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}@media(hover:hover){.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-transparent::file-selector-button{background-color:#0000}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-medium::file-selector-button{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}@media(hover:hover){.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-80:hover{opacity:.8}.hover\:opacity-100:hover{opacity:1}}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:ring-1:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}@media(min-width:40rem){.sm\:flex-row{flex-direction:row}.sm\:justify-end{justify-content:flex-end}:where(.sm\:space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse)))}.sm\:rounded-lg{border-radius:var(--radius-lg)}.sm\:text-left{text-align:left}}@media(min-width:48rem){.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}@media(prefers-color-scheme:dark){.dark\:scale-0{--tw-scale-x:0%;--tw-scale-y:0%;--tw-scale-z:0%;scale:var(--tw-scale-x) var(--tw-scale-y)}.dark\:scale-100{--tw-scale-x:100%;--tw-scale-y:100%;--tw-scale-z:100%;scale:var(--tw-scale-x) var(--tw-scale-y)}.dark\:-rotate-90{rotate:-90deg}.dark\:rotate-0{rotate:0deg}.dark\:border-green-900{border-color:var(--color-green-900)}.dark\:border-red-900{border-color:var(--color-red-900)}.dark\:bg-green-950{background-color:var(--color-green-950)}.dark\:bg-red-950{background-color:var(--color-red-950)}.dark\:bg-yellow-950{background-color:var(--color-yellow-950)}.dark\:text-green-200{color:var(--color-green-200)}.dark\:text-red-200{color:var(--color-red-200)}.dark\:text-yellow-200{color:var(--color-yellow-200)}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_tr\]\:border-b tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.\[\&_tr\:last-child\]\:border-0 tr:last-child{border-style:var(--tw-border-style);border-width:0}.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]){padding-right:calc(var(--spacing) * 0)}.\[\&\>\[role\=checkbox\]\]\:translate-y-\[2px\]>[role=checkbox]{--tw-translate-y:2px;translate:var(--tw-translate-x) var(--tw-translate-y)}.\[\&\>span\]\:line-clamp-1>span{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.\[\&\>svg\]\:size-4>svg{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&\>svg\]\:shrink-0>svg{flex-shrink:0}.\[\&\>tr\]\:last\:border-b-0>tr:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-duration{syntax:"*";inherits:false}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@keyframes spin{to{transform:rotate(360deg)}}
|