mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:01:08 +02:00
feat(shared-privacy): M1 — visibility foundation package
Scaffold the unified visibility/privacy layer introduced by docs/plans/
visibility-system.md. No module adopts it yet — this is the foundation
PR (M1). Module rollout lands in follow-ups starting with Library (M2).
What ships:
- @mana/shared-privacy package
- VisibilityLevel enum ('private' | 'space' | 'unlisted' | 'public')
- VisibilityLevelSchema + UnlistedTokenSchema (zod)
- defaultVisibilityFor(spaceType): personal → private, else → space
- predicates: canEmbedOnWebsite, isReachableByLink,
isVisibleToSpaceMember, canAiAccessCrossUser (always false in P1)
- generateUnlistedToken() — 32-char base64url, CSPRNG, ~192 bits
- VISIBILITY_METADATA: German labels + descriptions + phosphor icon
names so non-UI surfaces (audit logs, CLI) label levels consistently
- <VisibilityPicker> svelte component: compact lock/globe trigger with
4-option menu, full descriptions, optional compact + disabledLevels
- VisibilityChangedPayload type for the domain-event catalog (consumer
registers it when the first module adopts the system)
- .claude/guidelines/visibility.md — step-by-step for module authors
(schema migrations + store wiring + picker placement + embed resolver +
legacy isPublic migration), with a pre-PR checklist
- Plan-doc "Offene Fragen" section rewritten as "Designentscheidungen"
with the seven resolutions the user approved
- CLAUDE.md: shared-privacy listed in the packages table; visibility.md
listed in the guidelines table
- 15 unit tests covering predicates (one-and-only-one 'public' for
embed; phase-1 AI always-deny), defaults (personal vs multi-member,
null fallback), token uniqueness + schema round-trip
Key constraints honored:
- `visibility` stays plaintext (NOT added to the encryption registry)
so RLS predicates and publish resolvers can read it without the user's
master key
- Publish flow remains "decrypt client-side, inline plaintext into
snapshot" — the pattern picture.board already uses in embeds.ts
- Deny-by-default everywhere (personal default = private; unknown space
type defaults to private; cross-user AI always false)
Not in this PR (per plan):
- No schema migrations in any module (M2–M6)
- No RLS predicate updates (arrives with M2)
- No /settings/privacy overview (M7)
- No unlisted share routes (M8)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
91fd88e77d
commit
49935c9628
16 changed files with 1100 additions and 148 deletions
242
.claude/guidelines/visibility.md
Normal file
242
.claude/guidelines/visibility.md
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
# Visibility — Adding Visibility Control to a Module
|
||||
|
||||
How to adopt the unified visibility/privacy system for a module. Applies to every public-capable module (see `docs/plans/visibility-system.md` for the full list).
|
||||
|
||||
## TL;DR
|
||||
|
||||
Adding visibility control needs edits in **five places**:
|
||||
|
||||
1. **Dexie + mana-sync schema** — add `visibility`, `unlistedToken`, `visibilityChangedAt`, `visibilityChangedBy` columns
|
||||
2. **`types.ts`** — add `visibility: VisibilityLevel` to the local record type + converter
|
||||
3. **`stores/*.svelte.ts`** — add `setVisibility()` method + stamp default on create
|
||||
4. **Detail-View `.svelte`** — drop `<VisibilityPicker>` into the header
|
||||
5. **Embed resolver (if applicable)** — gate on `canEmbedOnWebsite`
|
||||
|
||||
Legacy `isPublic` flags are migrated in the same PR: `isPublic=true → visibility='public'`, else `'private'`.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ @mana/shared-privacy │
|
||||
│ VisibilityLevel = 'private'|'space'|'unlisted'|'public' │
|
||||
│ VisibilityLevelSchema (zod) │
|
||||
│ defaultVisibilityFor(spaceType) │
|
||||
│ canEmbedOnWebsite / isReachableByLink / … (predicates) │
|
||||
│ generateUnlistedToken() │
|
||||
│ <VisibilityPicker> │
|
||||
└───────────┬─────────────────────────────────┬────────────┘
|
||||
│ │
|
||||
┌────────▼────────┐ ┌─────────▼──────────────┐
|
||||
│ Module stores │ │ website/embeds.ts │
|
||||
│ - setVisibility │ │ - filter by predicate │
|
||||
│ - default on │ │ - decrypt + inline │
|
||||
│ create │ │ matching records │
|
||||
└──────────────────┘ └─────────────────────────┘
|
||||
│
|
||||
┌────────▼────────────┐
|
||||
│ Detail-View │
|
||||
│ <VisibilityPicker │
|
||||
│ level={...} │
|
||||
│ onChange={...} /> │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
`visibility` stays **plaintext** (not in the encryption registry) so RLS predicates and publish resolvers can read it without the user's master key.
|
||||
|
||||
## Step-by-step
|
||||
|
||||
### 1. Schema
|
||||
|
||||
**Dexie** — bump the module's table in `apps/mana/apps/web/src/lib/data/database.ts` (soft-migration):
|
||||
|
||||
```ts
|
||||
// v{N+1}: add visibility
|
||||
{
|
||||
version: N + 1,
|
||||
stores: {
|
||||
myTable: 'id, spaceId, visibility, createdAt, …', // add visibility to indexes if you'll query on it
|
||||
},
|
||||
upgrade: async (tx) => {
|
||||
await tx.table('myTable').toCollection().modify((r) => {
|
||||
r.visibility = 'private';
|
||||
});
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Postgres** — add columns to the mana-sync schema for this module (find the drizzle schema under `services/mana-sync/` or the per-module schema in `apps/api/`):
|
||||
|
||||
```sql
|
||||
alter table <schema>.<table>
|
||||
add column visibility text not null default 'private',
|
||||
add column unlisted_token text,
|
||||
add column visibility_changed_at timestamptz,
|
||||
add column visibility_changed_by text;
|
||||
```
|
||||
|
||||
Partial index for the common embed query:
|
||||
|
||||
```sql
|
||||
create index <table>_public_idx on <schema>.<table> (space_id)
|
||||
where visibility = 'public';
|
||||
```
|
||||
|
||||
### 2. Local types
|
||||
|
||||
```ts
|
||||
// types.ts
|
||||
import type { VisibilityLevel } from '@mana/shared-privacy';
|
||||
|
||||
export interface LocalMyRecord {
|
||||
id: string;
|
||||
// ... existing fields ...
|
||||
visibility: VisibilityLevel;
|
||||
unlistedToken?: string;
|
||||
visibilityChangedAt?: string;
|
||||
visibilityChangedBy?: string;
|
||||
}
|
||||
```
|
||||
|
||||
Update `toMyRecord()` converter in `queries.ts` to forward the fields.
|
||||
|
||||
### 3. Store
|
||||
|
||||
```ts
|
||||
// stores/my.svelte.ts
|
||||
import {
|
||||
VisibilityLevelSchema,
|
||||
defaultVisibilityFor,
|
||||
generateUnlistedToken,
|
||||
type VisibilityLevel,
|
||||
} from '@mana/shared-privacy';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { activeSpace } from '$lib/stores/space.svelte';
|
||||
|
||||
export const myStore = {
|
||||
async createRecord(input: CreateInput) {
|
||||
const now = new Date().toISOString();
|
||||
const record: LocalMyRecord = {
|
||||
id: crypto.randomUUID(),
|
||||
// ...
|
||||
visibility: defaultVisibilityFor(activeSpace.current?.type),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
await myTable.add(record);
|
||||
return record;
|
||||
},
|
||||
|
||||
async setVisibility(id: string, next: VisibilityLevel) {
|
||||
const existing = await myTable.get(id);
|
||||
if (!existing) throw new Error(`Record ${id} not found`);
|
||||
const before = existing.visibility;
|
||||
if (before === next) return;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const patch: Partial<LocalMyRecord> = {
|
||||
visibility: next,
|
||||
visibilityChangedAt: now,
|
||||
visibilityChangedBy: currentUserId(),
|
||||
updatedAt: now,
|
||||
};
|
||||
// Mint a fresh token on first transition to unlisted; wipe on leaving.
|
||||
if (next === 'unlisted' && !existing.unlistedToken) {
|
||||
patch.unlistedToken = generateUnlistedToken();
|
||||
} else if (next !== 'unlisted' && existing.unlistedToken) {
|
||||
patch.unlistedToken = undefined;
|
||||
}
|
||||
await myTable.update(id, patch);
|
||||
|
||||
emitDomainEvent('VisibilityChanged', '<appId>', '<collection>', id, {
|
||||
recordId: id,
|
||||
collection: '<collection>',
|
||||
before,
|
||||
after: next,
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Register the `VisibilityChanged` event type in `apps/mana/apps/web/src/lib/data/events/catalog.ts` once (not per-module); the payload comes from `@mana/shared-privacy`.
|
||||
|
||||
### 4. Detail-view UI
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { VisibilityPicker, type VisibilityLevel } from '@mana/shared-privacy';
|
||||
import { myStore } from '../stores/my.svelte';
|
||||
import type { MyRecord } from '../types';
|
||||
|
||||
let { record }: { record: MyRecord } = $props();
|
||||
|
||||
async function onVisibilityChange(next: VisibilityLevel) {
|
||||
await myStore.setVisibility(record.id, next);
|
||||
}
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<h1>{record.title}</h1>
|
||||
<VisibilityPicker level={record.visibility} onChange={onVisibilityChange} />
|
||||
</header>
|
||||
```
|
||||
|
||||
For list views, show a small lock/globe icon next to items whose visibility differs from the space default. `VISIBILITY_METADATA[level].icon` gives the Phosphor icon name.
|
||||
|
||||
### 5. Embed resolver (only for public-embeddable modules)
|
||||
|
||||
```ts
|
||||
// apps/mana/apps/web/src/lib/modules/website/embeds.ts
|
||||
import { canEmbedOnWebsite } from '@mana/shared-privacy';
|
||||
|
||||
async function resolveMyModule(props): Promise<EmbedItem[]> {
|
||||
let records = await db.table('myTable').toArray();
|
||||
records = records.filter((r) => !r.deletedAt && canEmbedOnWebsite(r.visibility ?? 'private'));
|
||||
// User filters (tags, status, date window) stack ON TOP — never replace.
|
||||
// ...
|
||||
const decrypted = await decryptRecords('myTable', records);
|
||||
return decrypted.map(toEmbedItem);
|
||||
}
|
||||
```
|
||||
|
||||
Register the new source in `packages/website-blocks/src/moduleEmbed/schema.ts` under `EmbedSourceSchema`.
|
||||
|
||||
### 6. Legacy `isPublic` migration (if your module had one)
|
||||
|
||||
In the same PR that adds visibility:
|
||||
|
||||
```ts
|
||||
// Dexie upgrade step
|
||||
await tx
|
||||
.table('myTable')
|
||||
.toCollection()
|
||||
.modify((r) => {
|
||||
r.visibility = r.isPublic === true ? 'public' : 'private';
|
||||
delete r.isPublic;
|
||||
});
|
||||
```
|
||||
|
||||
And the corresponding Postgres migration:
|
||||
|
||||
```sql
|
||||
update <schema>.<table> set visibility = 'public' where is_public = true;
|
||||
alter table <schema>.<table> drop column is_public;
|
||||
```
|
||||
|
||||
## Checklist before PR
|
||||
|
||||
- [ ] Dexie schema bumped, upgrade step migrates existing rows to `'private'` (or maps `isPublic`)
|
||||
- [ ] Postgres migration adds `visibility`, `unlisted_token`, `visibility_changed_at`, `visibility_changed_by`
|
||||
- [ ] Partial index on `(space_id) where visibility = 'public'` (if module is embeddable)
|
||||
- [ ] `visibility` **not** added to the encryption registry
|
||||
- [ ] Default on create via `defaultVisibilityFor(space.type)`
|
||||
- [ ] Store has `setVisibility()` + emits `VisibilityChanged`
|
||||
- [ ] `<VisibilityPicker>` in the detail view
|
||||
- [ ] Embed resolver (if applicable) gates on `canEmbedOnWebsite`
|
||||
- [ ] Tests: store `setVisibility` flips correctly; resolver filters out non-public records
|
||||
- [ ] `validate:all` passes
|
||||
|
||||
## Reference
|
||||
|
||||
- Plan + rationale: [`docs/plans/visibility-system.md`](../../docs/plans/visibility-system.md)
|
||||
- Package: `packages/shared-privacy/`
|
||||
|
|
@ -53,6 +53,7 @@ Always consult before changing code:
|
|||
| [`.claude/guidelines/testing.md`](.claude/guidelines/testing.md) | Vitest, mock factories |
|
||||
| [`.claude/guidelines/design-ux.md`](.claude/guidelines/design-ux.md) | UI patterns, a11y |
|
||||
| [`.claude/guidelines/ai-tools.md`](.claude/guidelines/ai-tools.md) | Adding AI tools to a module |
|
||||
| [`.claude/guidelines/visibility.md`](.claude/guidelines/visibility.md) | Adopting the visibility/privacy system per module |
|
||||
|
||||
## Development Quick Start
|
||||
|
||||
|
|
@ -154,6 +155,7 @@ Enforced by `pnpm run validate:turbo` (`scripts/validate-no-recursive-turbo.mjs`
|
|||
| `@mana/shared-ui` | React Native UI components |
|
||||
| `@mana/shared-theme` | Theme config |
|
||||
| `@mana/shared-i18n` | i18n |
|
||||
| `@mana/shared-privacy` | Unified visibility/privacy system: `VisibilityLevel` enum + zod schema + `<VisibilityPicker>` + predicates (`canEmbedOnWebsite`, …). Plan: [`docs/plans/visibility-system.md`](docs/plans/visibility-system.md). Rollout per-module, not yet adopted anywhere. |
|
||||
| `@mana/local-store` | Local-first store primitives — used by unified Mana, manavoxel, arcade, and shared-uload/-stores/-links |
|
||||
| `@mana/local-llm` | Browser-local LLM inference (transformers.js + Gemma 4 E2B, WebGPU). Powers `/llm-test` and the playground module. See [`packages/local-llm/CLAUDE.md`](packages/local-llm/CLAUDE.md) for the CSP requirements and the transformers.js v4 gotchas. |
|
||||
| `@mana/local-stt` | Browser-local speech-to-text (transformers.js + Whisper, WebGPU). Powers the QuickInputBar mic button. Same architecture as local-llm. See [`packages/local-stt/CLAUDE.md`](packages/local-stt/CLAUDE.md). |
|
||||
|
|
|
|||
|
|
@ -254,15 +254,29 @@ Breite Welle — alle Module, die noch public-relevant sind. Jedes ist ein klein
|
|||
- Per Modul: Share-Dialog "Link erstellen"
|
||||
- Neu erzeugte Tokens rotieren nicht automatisch — wer den Link weitergibt, akzeptiert permanente Exposure bis Revoke
|
||||
|
||||
## Offene Designfragen
|
||||
## Designentscheidungen (2026-04-23 festgeschrieben)
|
||||
|
||||
1. **Subressourcen-Redaction.** Calendar-Event mit Gästen: werden die Gästenamen beim Publish redacted? Vermutung: **ja**, vom Publish-Resolver. Pro Modul entscheiden.
|
||||
2. **Public-Items + Owner-Identität.** Ist der Name des Owners auf einem public Item sichtbar? Vermutung: **ja für Events** (Veranstalter), **nein für Todos** (Creator ist irrelevant). Pro Resolver.
|
||||
3. **AI-Agent-Zugriff.** Darf ein User-AI-Agent auf public Items anderer User zugreifen (Future-Feature)? Per `canEmbedOnWebsite`-ähnliches Predicate, aber ein neues: `canAiAccessCrossUser`. Nicht Phase 1.
|
||||
4. **Encryption-Registry-Update.** Für Module, die das Feld bekommen: Record-Body wie gehabt encrypted (weil auch bei public-Items will man die Dexie-Backup-Exports verschlüsselt haben). `visibility` ist das einzige plaintext-Feld. Publish-Flow bleibt "clientseitig entschlüsseln → inline".
|
||||
5. **Mehrere Websites pro Space.** Plan-Doc `website-builder.md` geht von 1 Website pro Space aus. Bei 2+ Websites per Space müsste Visibility pro-Website differenziert werden (`visibleOnSite: siteId[]` statt bool). **Nicht Phase 1.**
|
||||
6. **Preview-Mode im Editor.** Der Editor rendert selbst ohne Filter (Owner sieht alles). Der Publish-Preview muss die Filter anwenden, damit der User weiß, was wirklich public geht. Kleines Feature, auf M4 hängen.
|
||||
7. **Default-Migration.** Beim Erstmigration der 7 ad-hoc-Flags: alle `isPublic=false` werden `visibility='private'`. Das ist strikt — ein existierendes Private-Item bleibt privat. Aber: ein User, der bisher alles implizit behandelt hat ohne den Flag anzufassen, hat nichts public. OK-Verhalten.
|
||||
Die folgenden Fragen waren offen beim ersten Entwurf und wurden vor der Umsetzung entschieden.
|
||||
|
||||
1. **Subressourcen-Redaction.** Entschieden: **Whitelist, nicht Blacklist.** Publish-Resolver ziehen nur explizit freigegebene Felder in den Snapshot. Beim Calendar-Event: `{ title, startsAt, endsAt, location.publicAddress }`; das Gäste-Array wird auf `guestCount: number` degradiert. Pro-Feld-Freigabe (`publicFields: string[]` am Record) ist Phase 2.
|
||||
|
||||
2. **Owner-Identität auf public Items.** Entschieden: **Space-Setting, nicht per-Record.** Der Space bekommt ein Feld `publicDisplayName` (z. B. "Tills Gigs" oder "Anonym"). Publish-Snapshots zeigen nur diesen Namen — nie User-Real-Name, Avatar oder Email. Space ist ohnehin der Publish-Container; dort gehört die Identitätsentscheidung hin.
|
||||
|
||||
3. **AI-Agent-Zugriff cross-user.** Entschieden: **Phase 1 nein, Predicate vorbereitet.** `canAiAccessCrossUser(level)` im `@mana/shared-privacy`-Package returnt immer `false`. Wenn später ein Feature cross-user-Reads will, wird's pro Modul explizit freigeschaltet. Fließt nicht ins MVP.
|
||||
|
||||
4. **Encryption-Registry-Update.** Entschieden: **Record bleibt encrypted, nur `visibility` ist plaintext.** Publish-Flow entschlüsselt clientseitig und inlined plaintext in den Snapshot (heutiges Picture-Board-Muster). Vorteile: Dexie-Backups bleiben durchgehend encrypted, Zero-Knowledge-Mode funktioniert weiter, keine Re-Encryption-Migration beim Visibility-Toggle.
|
||||
|
||||
5. **Mehrere Websites pro Space.** Entschieden: **Phase 1 ignoriert das.** `visibility='public'` heißt "für das aktive Space-Publish-Target". Bei späterer Multi-Site kommt eine `siteIds`-Filter am Embed-Block, **nicht** am Record. Record-Modell bleibt simpel; Komplexität lebt im Publish-Layer.
|
||||
|
||||
6. **Preview-Mode im Editor.** Entschieden: **Zwei explizite Modi, Toggle im Editor.** "Bearbeiten" (Owner sieht alles, default) + "Als Besucher ansehen" (Embed-Filter werden angewandt). `previewAsPublic: boolean` im EditorView-State, den Embed-Renderer respektieren. Implementierung hängt an M4.
|
||||
|
||||
7. **Default-Migration der 7 ad-hoc `isPublic`-Flags.** Entschieden: **Strict Mapping, kein User-Prompt.** `isPublic === true` → `visibility='public'`. Alles andere → `visibility='private'` (nicht `space`, weil die existierenden Flags keine Space-Dimension hatten). Hard-Drop des alten Booleans in M6.
|
||||
|
||||
**Zusätzlich für M1 festgeschrieben:**
|
||||
|
||||
- **Unlisted-Token-Format:** 32-char base64url, generiert via `crypto.randomUUID()` + Base-Normalisierung. Rotiert NICHT automatisch. Revoke = Token auf NULL setzen und Visibility auf `private` zurückdrehen.
|
||||
- **Domain-Event:** `VisibilityChanged` mit Payload `{ recordId, collection, before, after, actor }`. Landet im `_events`-Log → integriert sich in Workbench-Timeline und AI-Revert-System.
|
||||
- **Rate-Limit-Warnung (optional, M7):** "mehr als 100 Records public in einer Minute" zeigt einen Toast "X Items wurden public gemacht — rückgängig machen?". Kein Hard-Block, nur Fat-Finger-Schutz.
|
||||
|
||||
## Anti-Patterns — was wir nicht bauen
|
||||
|
||||
|
|
|
|||
40
packages/shared-privacy/package.json
Normal file
40
packages/shared-privacy/package.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "@mana/shared-privacy",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
"**/*.svelte",
|
||||
"**/*.css"
|
||||
],
|
||||
"svelte": "./src/index.ts",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"svelte": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"type-check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@mana/shared-icons": "workspace:*",
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.7.3",
|
||||
"vitest": "^4.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.0.0"
|
||||
}
|
||||
}
|
||||
218
packages/shared-privacy/src/VisibilityPicker.svelte
Normal file
218
packages/shared-privacy/src/VisibilityPicker.svelte
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
<!--
|
||||
VisibilityPicker — one compact dropdown used in every module's detail
|
||||
view to toggle a record's visibility. The trigger is a lock/globe icon
|
||||
with the short label; opening it reveals all four levels with their
|
||||
descriptions.
|
||||
|
||||
Stateless — the consumer owns the current level and the onChange
|
||||
callback. Keeps the component reusable across stores and encryption
|
||||
boundaries.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Lock, UsersThree, LinkSimple, Globe, CaretDown } from '@mana/shared-icons';
|
||||
import type { Component } from 'svelte';
|
||||
import { VISIBILITY_LEVELS, VISIBILITY_METADATA, type VisibilityLevel } from './types';
|
||||
|
||||
interface Props {
|
||||
level: VisibilityLevel;
|
||||
onChange: (next: VisibilityLevel) => void;
|
||||
/** Hide specific levels — e.g. a single-user space has no `space` option. */
|
||||
disabledLevels?: VisibilityLevel[];
|
||||
/** Show only the icon, not the label. For tight layouts. */
|
||||
compact?: boolean;
|
||||
/** Disable the whole control. */
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
let { level, onChange, disabledLevels = [], compact = false, disabled = false }: Props = $props();
|
||||
|
||||
const ICON_MAP: Record<VisibilityLevel, Component> = {
|
||||
private: Lock,
|
||||
space: UsersThree,
|
||||
unlisted: LinkSimple,
|
||||
public: Globe,
|
||||
};
|
||||
|
||||
let open = $state(false);
|
||||
let triggerEl = $state<HTMLButtonElement | null>(null);
|
||||
|
||||
const current = $derived(VISIBILITY_METADATA[level]);
|
||||
const CurrentIcon = $derived(ICON_MAP[level]);
|
||||
|
||||
function toggle() {
|
||||
if (disabled) return;
|
||||
open = !open;
|
||||
}
|
||||
|
||||
function select(next: VisibilityLevel) {
|
||||
open = false;
|
||||
if (next === level) return;
|
||||
onChange(next);
|
||||
}
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape' && open) {
|
||||
e.preventDefault();
|
||||
open = false;
|
||||
triggerEl?.focus();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="vp" onkeydown={onKeyDown} role="presentation">
|
||||
<button
|
||||
bind:this={triggerEl}
|
||||
class="vp__trigger"
|
||||
class:vp__trigger--compact={compact}
|
||||
class:vp__trigger--open={open}
|
||||
onclick={toggle}
|
||||
{disabled}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={open}
|
||||
title={current.description}
|
||||
>
|
||||
<CurrentIcon size={14} weight="bold" />
|
||||
{#if !compact}
|
||||
<span class="vp__label">{current.label}</span>
|
||||
<span class="vp__caret"><CaretDown size={10} weight="bold" /></span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if open}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div class="vp__backdrop" onclick={() => (open = false)}></div>
|
||||
<div class="vp__menu" role="menu">
|
||||
{#each VISIBILITY_LEVELS as lvl (lvl)}
|
||||
{@const meta = VISIBILITY_METADATA[lvl]}
|
||||
{@const Icon = ICON_MAP[lvl]}
|
||||
{@const isDisabled = disabledLevels.includes(lvl)}
|
||||
<button
|
||||
class="vp__opt"
|
||||
class:vp__opt--active={lvl === level}
|
||||
disabled={isDisabled}
|
||||
role="menuitemradio"
|
||||
aria-checked={lvl === level}
|
||||
onclick={() => select(lvl)}
|
||||
>
|
||||
<span class="vp__opt-icon"><Icon size={16} weight="bold" /></span>
|
||||
<span class="vp__opt-text">
|
||||
<span class="vp__opt-label">{meta.label}</span>
|
||||
<span class="vp__opt-desc">{meta.description}</span>
|
||||
</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.vp {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
.vp__trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.25rem 0.55rem;
|
||||
background: transparent;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 0.375rem;
|
||||
color: inherit;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 0.15s,
|
||||
border-color 0.15s,
|
||||
opacity 0.15s;
|
||||
}
|
||||
.vp__trigger:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(255, 255, 255, 0.22);
|
||||
}
|
||||
.vp__trigger--open {
|
||||
background: rgba(99, 102, 241, 0.12);
|
||||
border-color: rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
.vp__trigger:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.vp__trigger--compact {
|
||||
padding: 0.25rem 0.35rem;
|
||||
}
|
||||
.vp__label {
|
||||
line-height: 1;
|
||||
}
|
||||
.vp__caret {
|
||||
display: inline-flex;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.vp__backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
background: transparent;
|
||||
}
|
||||
.vp__menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 0.35rem);
|
||||
right: 0;
|
||||
z-index: 60;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 16rem;
|
||||
padding: 0.25rem;
|
||||
border-radius: 0.5rem;
|
||||
background: rgb(20, 24, 32);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
.vp__opt {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.55rem;
|
||||
padding: 0.5rem 0.625rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: background 0.1s;
|
||||
}
|
||||
.vp__opt:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
.vp__opt--active {
|
||||
background: rgba(99, 102, 241, 0.12);
|
||||
}
|
||||
.vp__opt:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.vp__opt-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-top: 0.1rem;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.vp__opt-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.vp__opt-label {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.vp__opt-desc {
|
||||
font-size: 0.7rem;
|
||||
opacity: 0.6;
|
||||
line-height: 1.3;
|
||||
}
|
||||
</style>
|
||||
24
packages/shared-privacy/src/defaults.test.ts
Normal file
24
packages/shared-privacy/src/defaults.test.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { defaultVisibilityFor } from './defaults';
|
||||
|
||||
describe('defaultVisibilityFor', () => {
|
||||
it('returns private for personal space', () => {
|
||||
expect(defaultVisibilityFor('personal')).toBe('private');
|
||||
});
|
||||
|
||||
it('returns space for multi-member types', () => {
|
||||
expect(defaultVisibilityFor('team')).toBe('space');
|
||||
expect(defaultVisibilityFor('club')).toBe('space');
|
||||
expect(defaultVisibilityFor('firma')).toBe('space');
|
||||
});
|
||||
|
||||
it('returns space for unknown multi-member types (safe assumption)', () => {
|
||||
expect(defaultVisibilityFor('band')).toBe('space');
|
||||
});
|
||||
|
||||
it('falls back to private when space type is missing', () => {
|
||||
expect(defaultVisibilityFor(null)).toBe('private');
|
||||
expect(defaultVisibilityFor(undefined)).toBe('private');
|
||||
expect(defaultVisibilityFor('')).toBe('private');
|
||||
});
|
||||
});
|
||||
20
packages/shared-privacy/src/defaults.ts
Normal file
20
packages/shared-privacy/src/defaults.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import type { VisibilityLevel } from './types';
|
||||
|
||||
/**
|
||||
* Default visibility for newly-created records, derived from the space
|
||||
* type. Personal spaces stay `private` so a fresh note or task doesn't
|
||||
* accidentally leak to cohabitants of a team space; multi-member spaces
|
||||
* (team, club, firma, …) default to `space` so collaboration works
|
||||
* without requiring a manual toggle on every write.
|
||||
*
|
||||
* Accepts `null`/`undefined`/unknown strings and treats them as personal
|
||||
* — the safer direction. Callers that know the space type pass it
|
||||
* directly; callers that don't (e.g. during sync-apply) fall back to
|
||||
* 'private'.
|
||||
*/
|
||||
export function defaultVisibilityFor(spaceType: string | null | undefined): VisibilityLevel {
|
||||
if (!spaceType) return 'private';
|
||||
if (spaceType === 'personal') return 'private';
|
||||
// team, club, firma, or any future multi-member type.
|
||||
return 'space';
|
||||
}
|
||||
35
packages/shared-privacy/src/index.ts
Normal file
35
packages/shared-privacy/src/index.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* @mana/shared-privacy
|
||||
*
|
||||
* Unified visibility/privacy primitives for every Mana module. Provides:
|
||||
*
|
||||
* - VisibilityLevel enum: 'private' | 'space' | 'unlisted' | 'public'
|
||||
* - Zod schema for validation at the record/schema layer
|
||||
* - Default helper (derives private vs space from the active space type)
|
||||
* - Predicates for publish-time gating (canEmbedOnWebsite, …)
|
||||
* - Unlisted-token generator (32-char base64url, CSPRNG)
|
||||
* - <VisibilityPicker> Svelte component for the consistent UI control
|
||||
*
|
||||
* Design + rollout: docs/plans/visibility-system.md.
|
||||
*
|
||||
* Import path stays flat:
|
||||
* import {
|
||||
* VisibilityLevelSchema,
|
||||
* canEmbedOnWebsite,
|
||||
* VisibilityPicker,
|
||||
* } from '@mana/shared-privacy';
|
||||
*/
|
||||
|
||||
export type { VisibilityLevel, VisibilityMeta, VisibilityChangedPayload } from './types';
|
||||
export { VISIBILITY_LEVELS, VISIBILITY_METADATA } from './types';
|
||||
export { VisibilityLevelSchema, UnlistedTokenSchema } from './schema';
|
||||
export { defaultVisibilityFor } from './defaults';
|
||||
export {
|
||||
canEmbedOnWebsite,
|
||||
isReachableByLink,
|
||||
isVisibleToSpaceMember,
|
||||
canAiAccessCrossUser,
|
||||
} from './predicates';
|
||||
export { generateUnlistedToken } from './tokens';
|
||||
|
||||
export { default as VisibilityPicker } from './VisibilityPicker.svelte';
|
||||
45
packages/shared-privacy/src/predicates.test.ts
Normal file
45
packages/shared-privacy/src/predicates.test.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
canEmbedOnWebsite,
|
||||
isReachableByLink,
|
||||
isVisibleToSpaceMember,
|
||||
canAiAccessCrossUser,
|
||||
} from './predicates';
|
||||
import { VISIBILITY_LEVELS } from './types';
|
||||
|
||||
describe('canEmbedOnWebsite', () => {
|
||||
it('allows only public', () => {
|
||||
expect(canEmbedOnWebsite('public')).toBe(true);
|
||||
expect(canEmbedOnWebsite('unlisted')).toBe(false);
|
||||
expect(canEmbedOnWebsite('space')).toBe(false);
|
||||
expect(canEmbedOnWebsite('private')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isReachableByLink', () => {
|
||||
it('allows public and unlisted', () => {
|
||||
expect(isReachableByLink('public')).toBe(true);
|
||||
expect(isReachableByLink('unlisted')).toBe(true);
|
||||
});
|
||||
it('rejects space and private', () => {
|
||||
expect(isReachableByLink('space')).toBe(false);
|
||||
expect(isReachableByLink('private')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isVisibleToSpaceMember', () => {
|
||||
it('allows everything except private', () => {
|
||||
expect(isVisibleToSpaceMember('space')).toBe(true);
|
||||
expect(isVisibleToSpaceMember('unlisted')).toBe(true);
|
||||
expect(isVisibleToSpaceMember('public')).toBe(true);
|
||||
expect(isVisibleToSpaceMember('private')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('canAiAccessCrossUser', () => {
|
||||
it('denies for every level in Phase 1', () => {
|
||||
for (const lvl of VISIBILITY_LEVELS) {
|
||||
expect(canAiAccessCrossUser(lvl)).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
43
packages/shared-privacy/src/predicates.ts
Normal file
43
packages/shared-privacy/src/predicates.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import type { VisibilityLevel } from './types';
|
||||
|
||||
/**
|
||||
* Can this record be embedded on a published website? This is the
|
||||
* strictest exposure — a public website snapshot is readable by any
|
||||
* anonymous visitor, so gate hard on `public` only. Unlisted is
|
||||
* link-sharing, not website-embedding.
|
||||
*
|
||||
* Every embed resolver in `apps/mana/apps/web/src/lib/modules/website/
|
||||
* embeds.ts` must call this before inlining records into the snapshot.
|
||||
*/
|
||||
export function canEmbedOnWebsite(level: VisibilityLevel): boolean {
|
||||
return level === 'public';
|
||||
}
|
||||
|
||||
/**
|
||||
* Can this record be fetched via a direct unlisted-token link? Includes
|
||||
* `public` because a public record is reachable by link too (it just
|
||||
* also appears in embeds).
|
||||
*/
|
||||
export function isReachableByLink(level: VisibilityLevel): boolean {
|
||||
return level === 'public' || level === 'unlisted';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this record visible to other members of the owner's space, under
|
||||
* the normal `spaceModulePermissions` matrix? All non-private levels
|
||||
* are. Private records are owner-only, even inside multi-member spaces.
|
||||
*/
|
||||
export function isVisibleToSpaceMember(level: VisibilityLevel): boolean {
|
||||
return level !== 'private';
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder for a future cross-user AI-agent feature (see
|
||||
* docs/plans/visibility-system.md §3). Always returns false in Phase 1
|
||||
* so no current AI code path accidentally leaks data. When we're ready
|
||||
* to let agents read public cross-user records, flip this per-module
|
||||
* with an explicit opt-in.
|
||||
*/
|
||||
export function canAiAccessCrossUser(_level: VisibilityLevel): boolean {
|
||||
return false;
|
||||
}
|
||||
23
packages/shared-privacy/src/schema.ts
Normal file
23
packages/shared-privacy/src/schema.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Zod schema for the visibility enum. Use this in module record schemas
|
||||
* so every table validates the same way on writes.
|
||||
*
|
||||
* export const TaskSchema = z.object({
|
||||
* id: z.string().uuid(),
|
||||
* title: z.string(),
|
||||
* visibility: VisibilityLevelSchema,
|
||||
* ...
|
||||
* });
|
||||
*/
|
||||
export const VisibilityLevelSchema = z.enum(['private', 'space', 'unlisted', 'public']);
|
||||
|
||||
/**
|
||||
* Unlisted-token shape — 32 base64url chars (see tokens.ts). Zod check
|
||||
* enforces format so a corrupt/shortened token from a manual edit gets
|
||||
* rejected at the schema layer.
|
||||
*/
|
||||
export const UnlistedTokenSchema = z
|
||||
.string()
|
||||
.regex(/^[A-Za-z0-9_-]{32}$/, 'must be a 32-char base64url token');
|
||||
39
packages/shared-privacy/src/tokens.test.ts
Normal file
39
packages/shared-privacy/src/tokens.test.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { generateUnlistedToken } from './tokens';
|
||||
import { UnlistedTokenSchema } from './schema';
|
||||
|
||||
describe('generateUnlistedToken', () => {
|
||||
it('returns a 32-char base64url string', () => {
|
||||
const token = generateUnlistedToken();
|
||||
expect(token).toHaveLength(32);
|
||||
expect(token).toMatch(/^[A-Za-z0-9_-]+$/);
|
||||
});
|
||||
|
||||
it('passes the UnlistedTokenSchema', () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const token = generateUnlistedToken();
|
||||
expect(() => UnlistedTokenSchema.parse(token)).not.toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
it('is unique across many calls (entropy check)', () => {
|
||||
const tokens = new Set<string>();
|
||||
for (let i = 0; i < 1000; i++) tokens.add(generateUnlistedToken());
|
||||
expect(tokens.size).toBe(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UnlistedTokenSchema', () => {
|
||||
it('rejects tokens that are too short', () => {
|
||||
expect(() => UnlistedTokenSchema.parse('short')).toThrow();
|
||||
});
|
||||
|
||||
it('rejects tokens with invalid chars', () => {
|
||||
expect(() => UnlistedTokenSchema.parse('a/b+c=d'.padEnd(32, 'x'))).toThrow();
|
||||
});
|
||||
|
||||
it('accepts 32-char base64url', () => {
|
||||
expect(() => UnlistedTokenSchema.parse('A'.repeat(32))).not.toThrow();
|
||||
expect(() => UnlistedTokenSchema.parse('-_'.repeat(16))).not.toThrow();
|
||||
});
|
||||
});
|
||||
25
packages/shared-privacy/src/tokens.ts
Normal file
25
packages/shared-privacy/src/tokens.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Generate a URL-safe 32-character share token for unlisted-mode
|
||||
* records. Randomness comes from `crypto.getRandomValues` (CSPRNG on
|
||||
* every target Mana runs on: modern browsers, Bun, Node ≥18).
|
||||
*
|
||||
* 24 random bytes encode to exactly 32 base64url characters, so we get
|
||||
* ~192 bits of entropy — far more than enough to resist enumeration
|
||||
* attacks on an unlisted-link endpoint.
|
||||
*
|
||||
* Token rotation is NOT automatic. To revoke a share: unset the token
|
||||
* column and flip visibility back to 'private'. Regenerating is a
|
||||
* deliberate user action (e.g. "neu generieren" button in the share
|
||||
* dialog).
|
||||
*/
|
||||
export function generateUnlistedToken(): string {
|
||||
const bytes = new Uint8Array(24);
|
||||
crypto.getRandomValues(bytes);
|
||||
return base64urlEncode(bytes);
|
||||
}
|
||||
|
||||
function base64urlEncode(bytes: Uint8Array): string {
|
||||
let bin = '';
|
||||
for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]);
|
||||
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||
}
|
||||
71
packages/shared-privacy/src/types.ts
Normal file
71
packages/shared-privacy/src/types.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* Canonical visibility levels for any user-owned record in Mana.
|
||||
*
|
||||
* See docs/plans/visibility-system.md for the full design. Short form:
|
||||
*
|
||||
* private — only the owner (personal space) sees it
|
||||
* space — all space members per spaceModulePermissions
|
||||
* unlisted — reachable via direct link + token; not listed, noindex
|
||||
* public — embeddable on websites, discoverable to anonymous visitors
|
||||
*/
|
||||
export type VisibilityLevel = 'private' | 'space' | 'unlisted' | 'public';
|
||||
|
||||
/** Iteration-safe ordering. Used by the picker to render radio-list choices. */
|
||||
export const VISIBILITY_LEVELS: readonly VisibilityLevel[] = [
|
||||
'private',
|
||||
'space',
|
||||
'unlisted',
|
||||
'public',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* UI-agnostic descriptors so non-Svelte surfaces (CLI, audit logs, AI
|
||||
* agent explanations) can label a level consistently without reaching
|
||||
* into the Svelte component.
|
||||
*
|
||||
* German copy lives in the metadata because the whole Mana UI is German
|
||||
* today — i18n for privacy copy is a concrete follow-up when we add a
|
||||
* locale switch, not something to solve upfront.
|
||||
*/
|
||||
export interface VisibilityMeta {
|
||||
label: string;
|
||||
description: string;
|
||||
/** Phosphor icon name — resolved at render time via @mana/shared-icons. */
|
||||
icon: 'Lock' | 'UsersThree' | 'LinkSimple' | 'Globe';
|
||||
}
|
||||
|
||||
export const VISIBILITY_METADATA: Record<VisibilityLevel, VisibilityMeta> = {
|
||||
private: {
|
||||
label: 'Privat',
|
||||
description: 'Nur du siehst es.',
|
||||
icon: 'Lock',
|
||||
},
|
||||
space: {
|
||||
label: 'Bereich',
|
||||
description: 'Alle Mitglieder dieses Bereichs sehen es.',
|
||||
icon: 'UsersThree',
|
||||
},
|
||||
unlisted: {
|
||||
label: 'Per Link',
|
||||
description: 'Wer den Link hat, kann es sehen. Nicht gelistet.',
|
||||
icon: 'LinkSimple',
|
||||
},
|
||||
public: {
|
||||
label: 'Öffentlich',
|
||||
description: 'Auf deiner Website und für alle sichtbar.',
|
||||
icon: 'Globe',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Payload for the `VisibilityChanged` domain event — emitted by module
|
||||
* stores whenever a record's visibility flips. The event catalog in the
|
||||
* web app registers this type when the first module adopts the system
|
||||
* (see docs/plans/visibility-system.md §M2).
|
||||
*/
|
||||
export interface VisibilityChangedPayload {
|
||||
recordId: string;
|
||||
collection: string;
|
||||
before: VisibilityLevel;
|
||||
after: VisibilityLevel;
|
||||
}
|
||||
20
packages/shared-privacy/tsconfig.json
Normal file
20
packages/shared-privacy/tsconfig.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"types": ["svelte"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
371
pnpm-lock.yaml
generated
371
pnpm-lock.yaml
generated
|
|
@ -141,14 +141,14 @@ importers:
|
|||
version: link:../../../../packages/shared-landing-ui
|
||||
astro:
|
||||
specifier: ^5.16.0
|
||||
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
typescript:
|
||||
specifier: ^5.9.2
|
||||
version: 5.9.3
|
||||
devDependencies:
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.18
|
||||
version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
|
@ -157,13 +157,13 @@ importers:
|
|||
version: 20.19.39
|
||||
eslint:
|
||||
specifier: ^9.0.0
|
||||
version: 9.39.4(jiti@2.6.1)
|
||||
version: 9.39.4(jiti@1.21.7)
|
||||
eslint-config-prettier:
|
||||
specifier: ^9.1.0
|
||||
version: 9.1.2(eslint@9.39.4(jiti@2.6.1))
|
||||
version: 9.1.2(eslint@9.39.4(jiti@1.21.7))
|
||||
eslint-plugin-astro:
|
||||
specifier: ^1.0.0
|
||||
version: 1.6.0(eslint@9.39.4(jiti@2.6.1))
|
||||
version: 1.6.0(eslint@9.39.4(jiti@1.21.7))
|
||||
prettier:
|
||||
specifier: ^3.6.2
|
||||
version: 3.8.1
|
||||
|
|
@ -256,10 +256,10 @@ importers:
|
|||
version: 3.7.2
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
astro:
|
||||
specifier: ^5.16.11
|
||||
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
tailwindcss:
|
||||
specifier: ^3.4.17
|
||||
version: 3.4.19(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
|
@ -2230,6 +2230,28 @@ importers:
|
|||
specifier: ^5.7.2
|
||||
version: 5.9.3
|
||||
|
||||
packages/shared-privacy:
|
||||
dependencies:
|
||||
'@mana/shared-icons':
|
||||
specifier: workspace:*
|
||||
version: link:../shared-icons
|
||||
zod:
|
||||
specifier: ^3.25.76
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
svelte:
|
||||
specifier: ^5.0.0
|
||||
version: 5.55.1
|
||||
svelte-check:
|
||||
specifier: ^4.0.0
|
||||
version: 4.4.6(picomatch@4.0.4)(svelte@5.55.1)(typescript@5.9.3)
|
||||
typescript:
|
||||
specifier: ^5.7.3
|
||||
version: 5.9.3
|
||||
vitest:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.3(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(jsdom@29.0.2(@noble/hashes@2.0.1))(vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
||||
packages/shared-pwa:
|
||||
devDependencies:
|
||||
'@vite-pwa/sveltekit':
|
||||
|
|
@ -17328,6 +17350,16 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
astro: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
autoprefixer: 10.4.27(postcss@8.5.8)
|
||||
postcss: 8.5.8
|
||||
postcss-load-config: 4.0.2(postcss@8.5.8)
|
||||
tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
astro: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
|
|
@ -17348,16 +17380,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
astro: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
autoprefixer: 10.4.27(postcss@8.5.8)
|
||||
postcss: 8.5.8
|
||||
postcss-load-config: 4.0.2(postcss@8.5.8)
|
||||
tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
astro: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
|
|
@ -19529,6 +19551,11 @@ snapshots:
|
|||
'@esbuild/win32-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@1.21.7))':
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@1.21.7)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))':
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
|
@ -24608,6 +24635,108 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.1
|
||||
'@astrojs/internal-helpers': 0.7.6
|
||||
'@astrojs/markdown-remark': 6.3.11
|
||||
'@astrojs/telemetry': 3.3.0
|
||||
'@capsizecss/unpack': 4.0.0
|
||||
'@oslojs/encoding': 1.1.0
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
|
||||
acorn: 8.16.0
|
||||
aria-query: 5.3.2
|
||||
axobject-query: 4.1.0
|
||||
boxen: 8.0.1
|
||||
ci-info: 4.4.0
|
||||
clsx: 2.1.1
|
||||
common-ancestor-path: 1.0.1
|
||||
cookie: 1.1.1
|
||||
cssesc: 3.0.0
|
||||
debug: 4.4.3
|
||||
deterministic-object-hash: 2.0.2
|
||||
devalue: 5.7.0
|
||||
diff: 8.0.4
|
||||
dlv: 1.1.3
|
||||
dset: 3.1.4
|
||||
es-module-lexer: 1.7.0
|
||||
esbuild: 0.27.7
|
||||
estree-walker: 3.0.3
|
||||
flattie: 1.1.1
|
||||
fontace: 0.4.1
|
||||
github-slugger: 2.0.0
|
||||
html-escaper: 3.0.3
|
||||
http-cache-semantics: 4.2.0
|
||||
import-meta-resolve: 4.2.0
|
||||
js-yaml: 4.1.1
|
||||
magic-string: 0.30.21
|
||||
magicast: 0.5.2
|
||||
mrmime: 2.0.1
|
||||
neotraverse: 0.6.18
|
||||
p-limit: 6.2.0
|
||||
p-queue: 8.1.1
|
||||
package-manager-detector: 1.6.0
|
||||
piccolore: 0.1.3
|
||||
picomatch: 4.0.4
|
||||
prompts: 2.4.2
|
||||
rehype: 13.0.2
|
||||
semver: 7.7.4
|
||||
shiki: 3.23.0
|
||||
smol-toml: 1.6.1
|
||||
svgo: 4.0.1
|
||||
tinyexec: 1.0.4
|
||||
tinyglobby: 0.2.15
|
||||
tsconfck: 3.1.6(typescript@5.9.3)
|
||||
ultrahtml: 1.6.0
|
||||
unifont: 0.7.4
|
||||
unist-util-visit: 5.1.0
|
||||
unstorage: 1.17.5(@azure/storage-blob@12.31.0)(ioredis@5.10.1)
|
||||
vfile: 6.0.3
|
||||
vite: 6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vitefu: 1.1.3(vite@6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
xxhash-wasm: 1.1.0
|
||||
yargs-parser: 21.1.1
|
||||
yocto-spinner: 0.2.3
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.25.2(zod@3.25.76)
|
||||
zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76)
|
||||
optionalDependencies:
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
- '@azure/data-tables'
|
||||
- '@azure/identity'
|
||||
- '@azure/keyvault-secrets'
|
||||
- '@azure/storage-blob'
|
||||
- '@capacitor/preferences'
|
||||
- '@deno/kv'
|
||||
- '@netlify/blobs'
|
||||
- '@planetscale/database'
|
||||
- '@types/node'
|
||||
- '@upstash/redis'
|
||||
- '@vercel/blob'
|
||||
- '@vercel/functions'
|
||||
- '@vercel/kv'
|
||||
- aws4fetch
|
||||
- db0
|
||||
- idb-keyval
|
||||
- ioredis
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- rollup
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- typescript
|
||||
- uploadthing
|
||||
- yaml
|
||||
|
||||
astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.1
|
||||
|
|
@ -24812,108 +24941,6 @@ snapshots:
|
|||
- uploadthing
|
||||
- yaml
|
||||
|
||||
astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.1
|
||||
'@astrojs/internal-helpers': 0.7.6
|
||||
'@astrojs/markdown-remark': 6.3.11
|
||||
'@astrojs/telemetry': 3.3.0
|
||||
'@capsizecss/unpack': 4.0.0
|
||||
'@oslojs/encoding': 1.1.0
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
|
||||
acorn: 8.16.0
|
||||
aria-query: 5.3.2
|
||||
axobject-query: 4.1.0
|
||||
boxen: 8.0.1
|
||||
ci-info: 4.4.0
|
||||
clsx: 2.1.1
|
||||
common-ancestor-path: 1.0.1
|
||||
cookie: 1.1.1
|
||||
cssesc: 3.0.0
|
||||
debug: 4.4.3
|
||||
deterministic-object-hash: 2.0.2
|
||||
devalue: 5.7.0
|
||||
diff: 8.0.4
|
||||
dlv: 1.1.3
|
||||
dset: 3.1.4
|
||||
es-module-lexer: 1.7.0
|
||||
esbuild: 0.27.7
|
||||
estree-walker: 3.0.3
|
||||
flattie: 1.1.1
|
||||
fontace: 0.4.1
|
||||
github-slugger: 2.0.0
|
||||
html-escaper: 3.0.3
|
||||
http-cache-semantics: 4.2.0
|
||||
import-meta-resolve: 4.2.0
|
||||
js-yaml: 4.1.1
|
||||
magic-string: 0.30.21
|
||||
magicast: 0.5.2
|
||||
mrmime: 2.0.1
|
||||
neotraverse: 0.6.18
|
||||
p-limit: 6.2.0
|
||||
p-queue: 8.1.1
|
||||
package-manager-detector: 1.6.0
|
||||
piccolore: 0.1.3
|
||||
picomatch: 4.0.4
|
||||
prompts: 2.4.2
|
||||
rehype: 13.0.2
|
||||
semver: 7.7.4
|
||||
shiki: 3.23.0
|
||||
smol-toml: 1.6.1
|
||||
svgo: 4.0.1
|
||||
tinyexec: 1.0.4
|
||||
tinyglobby: 0.2.15
|
||||
tsconfck: 3.1.6(typescript@5.9.3)
|
||||
ultrahtml: 1.6.0
|
||||
unifont: 0.7.4
|
||||
unist-util-visit: 5.1.0
|
||||
unstorage: 1.17.5(@azure/storage-blob@12.31.0)(ioredis@5.10.1)
|
||||
vfile: 6.0.3
|
||||
vite: 6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vitefu: 1.1.3(vite@6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
xxhash-wasm: 1.1.0
|
||||
yargs-parser: 21.1.1
|
||||
yocto-spinner: 0.2.3
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.25.2(zod@3.25.76)
|
||||
zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76)
|
||||
optionalDependencies:
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
- '@azure/data-tables'
|
||||
- '@azure/identity'
|
||||
- '@azure/keyvault-secrets'
|
||||
- '@azure/storage-blob'
|
||||
- '@capacitor/preferences'
|
||||
- '@deno/kv'
|
||||
- '@netlify/blobs'
|
||||
- '@planetscale/database'
|
||||
- '@types/node'
|
||||
- '@upstash/redis'
|
||||
- '@vercel/blob'
|
||||
- '@vercel/functions'
|
||||
- '@vercel/kv'
|
||||
- aws4fetch
|
||||
- db0
|
||||
- idb-keyval
|
||||
- ioredis
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- rollup
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- typescript
|
||||
- uploadthing
|
||||
- yaml
|
||||
|
||||
astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.1
|
||||
|
|
@ -26745,6 +26772,11 @@ snapshots:
|
|||
eslint: 9.39.4(jiti@2.6.1)
|
||||
semver: 7.7.4
|
||||
|
||||
eslint-compat-utils@0.6.5(eslint@9.39.4(jiti@1.21.7)):
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@1.21.7)
|
||||
semver: 7.7.4
|
||||
|
||||
eslint-compat-utils@0.6.5(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
|
@ -26754,6 +26786,10 @@ snapshots:
|
|||
dependencies:
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
||||
eslint-config-prettier@9.1.2(eslint@9.39.4(jiti@1.21.7)):
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@1.21.7)
|
||||
|
||||
eslint-config-prettier@9.1.2(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
|
@ -26798,6 +26834,20 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-astro@1.6.0(eslint@9.39.4(jiti@1.21.7)):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7))
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@typescript-eslint/types': 8.58.0
|
||||
astro-eslint-parser: 1.4.0
|
||||
eslint: 9.39.4(jiti@1.21.7)
|
||||
eslint-compat-utils: 0.6.5(eslint@9.39.4(jiti@1.21.7))
|
||||
globals: 16.5.0
|
||||
postcss: 8.5.8
|
||||
postcss-selector-parser: 7.1.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-astro@1.6.0(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
|
||||
|
|
@ -26971,6 +27021,47 @@ snapshots:
|
|||
|
||||
eslint-visitor-keys@5.0.1: {}
|
||||
|
||||
eslint@9.39.4(jiti@1.21.7):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7))
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@eslint/config-array': 0.21.2
|
||||
'@eslint/config-helpers': 0.4.2
|
||||
'@eslint/core': 0.17.0
|
||||
'@eslint/eslintrc': 3.3.5
|
||||
'@eslint/js': 9.39.4
|
||||
'@eslint/plugin-kit': 0.4.1
|
||||
'@humanfs/node': 0.16.7
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@humanwhocodes/retry': 0.4.3
|
||||
'@types/estree': 1.0.8
|
||||
ajv: 6.14.0
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.3
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 8.4.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
espree: 10.4.0
|
||||
esquery: 1.7.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
file-entry-cache: 8.0.0
|
||||
find-up: 5.0.0
|
||||
glob-parent: 6.0.2
|
||||
ignore: 5.3.2
|
||||
imurmurhash: 0.1.4
|
||||
is-glob: 4.0.3
|
||||
json-stable-stringify-without-jsonify: 1.0.1
|
||||
lodash.merge: 4.6.2
|
||||
minimatch: 3.1.5
|
||||
natural-compare: 1.4.0
|
||||
optionator: 0.9.4
|
||||
optionalDependencies:
|
||||
jiti: 1.21.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint@9.39.4(jiti@2.6.1):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
|
||||
|
|
@ -34088,6 +34179,23 @@ snapshots:
|
|||
lightningcss: 1.32.0
|
||||
terser: 5.46.1
|
||||
|
||||
vite@6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.8
|
||||
rollup: 4.60.1
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 20.19.39
|
||||
fsevents: 2.3.3
|
||||
jiti: 1.21.7
|
||||
lightningcss: 1.32.0
|
||||
terser: 5.46.1
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
|
|
@ -34122,23 +34230,6 @@ snapshots:
|
|||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vite@6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.8
|
||||
rollup: 4.60.1
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 24.12.2
|
||||
fsevents: 2.3.3
|
||||
jiti: 1.21.7
|
||||
lightningcss: 1.32.0
|
||||
terser: 5.46.1
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
|
|
@ -34156,6 +34247,10 @@ snapshots:
|
|||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vitefu@1.1.3(vite@6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
vitefu@1.1.3(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
|
@ -34164,10 +34259,6 @@ snapshots:
|
|||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
vitefu@1.1.3(vite@6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
vitefu@1.1.3(vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue