Commit Graph

1586 Commits

Author SHA1 Message Date
Claude 7626dc95c1 perf: round 7 — photos timeline O(N²)→incremental, range-seek authz duplication, resources row-map clone
Benchmark-gated (equivalence + BEFORE/AFTER; results + reproduce commands in
benches/ROUND7.md):

- Photos timeline re-grouped + re-laid-out the whole accumulated library on
  every 60-item page (both `groups` and `photoRows` were $derived over the
  full list), Σ ≈ O(N²/60) main-thread work during a scroll. Pages arrive
  newest-first so grouping is append-only: the new PhotoTimeline
  (lib/utils/photoTimeline.ts) re-buckets only the fresh page and re-lays-out
  only changed groups, reusing untouched groups' cached rows, falling back to
  a full rebuild on any config/deletion/non-append change. The pure
  buildPhotoRows is the verbatim reference the gate holds it equal to at every
  page. 50×60 drain: 76 500 → 3 000 grouping ops (25.5x), 23.0 → 2.2 ms
  (10.6x).

- Range downloads paid authz + access-notify twice: download_file_impl
  resolves the file via get_file_with_perms, then the Range branch re-ran
  require_file + notify_file_accessed per request. Media/PDF viewers fetch
  exclusively via Range (one request per seek), so every seek in a scrub
  re-authorized an already-cleared file. Now routed through the non-perms
  get_file_range_preloaded (matching the share-landing + WebDAV range paths);
  the unused _with_perms range method is removed. The request-level gate still
  denies before the branch runs (bench asserts member granted, outsider
  denied). Per seek removed: WARM 0.67 µs, COLD 1362.66 µs — a grant-cascade
  drive-resolve query per seek for a shared-drive recipient on a cold cache.

- /api/folders/{id}/resources row→DTO mapping cloned row.name into the DTO
  though the row is owned; folders move it (fixed icons), files compute the
  name-derived icon/category classes first then move it. 500-row page:
  10.004 → 9.004 allocs/row (500 clones removed), output identical.

Deferred with rationale in ROUND7.md: thumbnail ACL-before-304 (security
posture — needs a security review, not a perf tweak), batch_operations
Arc<str>→String widening, list-view O(N²) on smaller lists, and the serial→
join! pairs (decide-by-bench with injected latency, per the round-6 rejection).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aJu9ghvuT8WqC31ZEGTBA
2026-07-18 13:11:41 +00:00
Claude f53fba42a1 Merge branch 'main' into claude/performance-optimization-round-6
Resolves the one conflict in file_blob_read_repository.rs's
suggest_files_by_name: main added the CALLER_CAN_READ_DRIVE authz scope
(caller_id param + drive-membership filter, AuthZ audit finding #1 — the
suggest query previously leaked names/paths across tenants), round 6
switched the same query's id/folder_id columns to binary UUID decode.
Kept both: main's authz structure (format! + CALLER_CAN_READ_DRIVE +
caller_id bind) with round 6's binary decode (fi.id / fi.folder_id, no
::text) so the query matches the FileRow = (Uuid, …) tuple. The
deliberately-text sites (min(fm.file_id::text), folder path lookup)
stay text. Verified: build + clippy -D warnings clean, 524 unit +
554 integration tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aJu9ghvuT8WqC31ZEGTBA
2026-07-18 10:07:42 +00:00
Claude 9729f033b2 perf: round 6 backend — CardDAV cursor streaming, borrowed NC id chain, binary UUID decode, one-alloc hex
Benchmark-gated (equivalence + BEFORE/AFTER in examples/bench_*, results
and reproduce commands in benches/ROUND6.md):

- CardDAV whole-book REPORT + depth-1 PROPFIND stream through a PG
  cursor (stream_contacts_by_book, 500-contact pages) instead of
  materialising every vCard twice: 8 000 contacts TTFB 37.4 → 7.6 ms
  (4.9x), peak heap 19.0 → 7.0 MiB (2.7x), wall -23%; REPORT and
  PROPFIND byte-identical to the buffered writers.

- NC numeric-id chain fully borrowed: get_or_create_file_ids/folder_ids
  take &[&str] and return HashMap<Uuid, i64>; batch_resolve_ids callers
  (PROPFIND pages, REPORT, trashbin, OCS search) pass id slices and look
  up via nc_id_of. 2.006 → 0.006 allocs/child (334x), 1.53x wall per
  500-child page. batch_check_favorites binds &[&str] as text[].

- file_blob_read_repository listing SELECTs drop id::text/folder_id::text
  server casts: rows decode binary Uuid (16 vs 36 bytes on the wire) and
  render once in row_to_file. A/B on 500-row pages: 1.225 → 1.044 ms
  mean (1.17x), p95 1.686 → 1.345 (bench_uuid_text_cast; single-row,
  param and min() sites left as-is deliberately).

- IncrementalHasher::finalize_hex renders through common::fmt::hex_lower
  instead of one format! per digest byte: 18 → 1 (md5) / 35 → 1 (sha256)
  allocs per chunk finalize, 14-15x wall.

- Share landing overlaps the access-count UPDATE with the unlock fetch
  via tokio::join! (one round-trip off every public link hit).

- REJECTED by benchmark and reverted: try_join_all fan-out of the
  batch-favorites authz pre-check — 42.6 → 56.4 ms cold, 0.15 → 0.23 ms
  warm against local-socket PG (bench_favorites_authz kept as evidence).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aJu9ghvuT8WqC31ZEGTBA
2026-07-18 09:03:33 +00:00
Dionisio Pozo 4f51cb7aab Merge pull request #613 from AtalayaLabs/claude/performance-optimization-round-5
perf: round 5 — CalDAV cursor streaming, SPA interning gaps, NC href prefix, per-request micro-allocs
2026-07-18 10:30:11 +02:00
Claude 61c9470981 perf(frontend): round 6 — coalesced progressive listing, in-place SvelteSet, batch fan-out, t() value cache
Four SPA hot-path fixes, each shipping with a vitest benchmark gate
(verbatim BEFORE replica + equivalence + perf assertion) so CI
re-verifies the win on every run:

- fetchFolderListing invoked onPage after EVERY 200-row page with the
  whole accumulated listing, and the files view re-sorts everything per
  emission — O(N²/page) main-thread work on large folders. Page one and
  the final page always emit; intermediates coalesce to one per 150 ms.
  25×200 load: 30.9 → 4.0 ms (7.8x), 65 000 → 5 200 sorted elements.

- selected/favoriteIds/sharedIds (files) and favoriteIds (recent) were
  $state<Set>s copied whole on every toggle. Now one SvelteSet each,
  mutated in place (the useSelection pattern): 1 000 toggles @ N=5 000
  771.9 → 1.9 ms (399x); one-toggle fan-out across 40 mounted rows
  40 → 3 re-runs when refining a select-all.

- batchDelete/moveInto awaited one request per item serially and
  probed listing.folders.find per id (O(N·M)). Now an id index built
  once + mapLimit(6) fan-out, failure semantics preserved: 100-item
  delete @ 5 ms RTT 525 → 89 ms (5.9x), 38 825 → 500 probes.

- t() re-split its dotted key and walked the nested dict on every call,
  and interpolate regex-scanned strings without placeholders. Resolved
  values now memoize per (dict, key) in a WeakMap + a {{ guard:
  20k mixed calls 22.7 → 8.6 ms (2.63x).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aJu9ghvuT8WqC31ZEGTBA
2026-07-18 00:54:38 +00:00
Claude 63cf6646d0 perf: round 5 — CalDAV cursor streaming, SPA interning gaps, NC href prefix, per-request micro-allocs
Seven benchmark-gated changes (benches/ROUND5.md; BEFORE/AFTER bench +
equivalence gate each, rollback rule as ROUND2-4 — two intermediate
CalDAV shapes measured worse and were themselves rolled back before
shipping):

- CalDAV whole-calendar responses (REPORT no-range/sync-collection,
  depth-1 collection PROPFIND, .ics GET): buffered double-residency →
  ONE window-ordered scan (MIN(start_time) OVER (PARTITION BY ical_uid))
  streamed through a PG cursor, pages cut at UID boundaries. TTFB
  23.3→11.0 ms (2.1x), peak heap 14.2→8.0 MiB at 4k events / 45→24 MiB
  at 12k, wall +9-15% (documented trade, ZIP-streaming class); both
  multistatus and ICS byte-identical to the buffered output. Rejected
  shapes kept in the doc: per-page GROUP-BY keyset (3-4x wall) and
  per-uid ANY hydration (~20 µs/index descent).
- SPA listing interning gaps: folder/recent/favorites resources handlers
  (and the WebDAV pseudo-root) called raw Arc::from per row for the
  closed display set ROUND3 interned — now intern_display/intern_mime,
  4→0 allocs/row, byte-identical Arc contents.
- NC PROPFIND child hrefs: username + parent path encoded once per
  request instead of per child (543→165 ns/row, 13→4 allocs); native
  WebDAV href drops its intermediate encode String.
- suggest enrichment: entity clone + field re-clones per keystroke row →
  consume + move (166.5→126.8 µs/200 rows, 20→7 allocs/row).
- list_readable_by returns the cache's Arc (246→128 ns warm hit, 4→0
  allocs) — deep Vec clone per DAV-selector request removed.
- CardDAV REPORT: borrowed props, reused href buffer, exact-size etag
  quoting (3.04→2.34 ms per 5k-contact getetag poll).
- Auth span records: user_id.to_string() per request ×3 →
  tracing::field::display.

Checks: cargo fmt, clippy --all-features --all-targets -D warnings,
cargo test --workspace (523 passed). Follow-ups (CardDAV streaming,
&[&str] id batches, ::text UUID casts A/B, share-landing join) recorded
in benches/ROUND5.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aJu9ghvuT8WqC31ZEGTBA
2026-07-17 15:19:00 +00:00
Dionisio Pozo 8aa013d3ee Merge pull request #605 from AtalayaLabs/claude/performance-optimization-analysis-sisndc
ROUND4: Row-path allocs, drive cache, CalDAV parse, PROPFIND emit, N+1 hydration, Azure streaming, faces bound
2026-07-17 15:56:34 +02:00
Claude 12dc648cff perf: round 4 — one-pass row paths, drive-selector cache, CalDAV single-parse, streamed Azure, batched hydration
Nine benchmark-gated changes (benches/ROUND4.md; every one ships with a
BEFORE/AFTER bench + equivalence gate, rollback rule as ROUND2/3):

- Row→entity path build: one-pass StoragePath::from_folder_and_name /
  from_joined + normalize_storage_name_owned + alloc-free Display —
  743→417 ns/file-row (1.78x), −5 allocs/row on every listing surface.
- WebDAV drive-selector: per-user readable_cache (single-flight, 30 s
  TTL, explicit invalidation incl. membership + group changes) replaces
  the grants join per request — 441 µs → 0.8 µs (~550x), 0 queries warm.
- CalDAV from_ical/update_ical_data: 8 full IcalParser runs per VEVENT
  → 1 (7.1x per PUT, 4.4x on 50-event imports); alloc-free split_vevents,
  chunk scan without the whole-body uppercase copy (1.4x), borrowed-key
  UID grouping (1.3x), REPORT props no longer cloned.
- PROPFIND emit: partition Vecs dropped (single-pass 404 list) + stack
  rendered RFC 3339/2822 dates, sizes, quoted etags (common::fmt,
  chrono-byte-identical, sweep-tested) on both DAV surfaces — 1.22x
  per page, 17.9→12.0 allocs/row.
- Grant-listing hydration: calendars/address books/playlists batch
  hydrate via = ANY($1) — 15 serial queries → 1 (~13x per sync poll).
- user-flags cache: get→insert → try_get_with single-flight (32→1
  queries per cold herd).
- Azure downloads: whole-blob Vec buffering → streamed SDK pages —
  TTFB 349→4 ms (87x), peak heap 480→1.9 MiB (254x) on 256 MiB blobs;
  new OXICLOUD_AZURE_ENDPOINT_URL override (Azurite/bench hook).
- Face indexing: unbounded per-image tokio::spawn → core-count
  semaphore, permit before blob read — peak heap 1175→176 MiB (6.7x).

Checks: cargo fmt, clippy --all-features --all-targets -D warnings,
cargo test --workspace (523 passed) + --features test_utils. hurl API
suite and dockerized integration DB not runnable in this environment —
left to CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aJu9ghvuT8WqC31ZEGTBA
2026-07-17 13:48:37 +00:00
Dionisio Pozo 807536b945 Merge pull request #601 from EdouardVanbelle/security/grants
security/grants
2026-07-17 13:31:03 +02:00
Dionisio Pozo 8a73607229 Merge pull request #604 from AtalayaLabs/claude/performance-optimization-analysis-c8nytj
Round 3: 12 perf optimizations — listing/timeline/auth/cache/DTO
2026-07-17 13:13:18 +02:00
Claude cd4c62042a perf: keyset/LATERAL SQL shapes, auth+blob-cache single-flight, spool buffers, DTO interning
Round 3 of benchmark-gated optimizations (benches/ROUND3.md; every change
gated by a before/after benchmark — an AFTER that did not beat its BEFORE
was to be rolled back; none needed it. Equivalence gates assert identical
row sequences / byte-identical output on every behavior-preserving rewrite):

DB hot paths (local PG16, EXPLAIN-verified):
- Web-UI listing (list_resources_paged): cursor pushed INSIDE the
  folders/files UNION-ALL branches as sargable row-value comparisons with
  per-branch ORDER/LIMIT + two partial expression indexes
  (folder_id, LOWER(name), id). 20k-entry folder: 26.6 -> 1.3 ms/page
  (19.5x); other sort modes at parity or better. New migration
  20260918000000. [benches/LISTING-KEYSET.md section in ROUND3]
- Photos timeline (list_media_files): per-drive CROSS JOIN LATERAL top-N
  on the timeline index, joins moved above the top-N. 50k-photo library:
  97.4 -> 1.6 ms/page (55.7x). The old "LIMIT stops the scan early"
  comment was refuted by EXPLAIN.
- PROPFIND sub-folders (both DAV surfaces): keyset list_folders_batch off
  idx_folders_unique_name replaces COUNT(*) OVER() + LIMIT/OFFSET
  (5k dirs: 79.7 -> 17.9 ms full walk, 4.5x).

Concurrency:
- Basic-auth cache single-flight (moka try_get_with): 8 concurrent DAV
  connections at TTL expiry paid 8 Argon2id runs (2.6 s CPU + 8x64 MiB);
  now 1 (300 ms). Failed verifications remain uncached.
- CachedBlobBackend per-hash single-flight + unique tmp names: 16
  concurrent cold readers = 16 full remote downloads racing truncating
  writes on ONE deterministic .tmp (corruptible cache); now 1 download
  (16x less egress, 2.8x wall on a shared link) and torn files can never
  be renamed into the cache.

I/O and allocations:
- Chunk-assembly reads 64K -> 512K buffers (2.3x, 8x fewer syscalls);
  chunk-spool writes via BufWriter 512K (5.6x, 32x fewer syscalls).
- S3/Azure put_blob_from_bytes_unsynced overrides: dedup settle no longer
  pays a HEAD probe per new chunk (2 RTT -> 1, 1.8x); Azure stops copying
  every chunk (Bytes -> Body, -0.44 ms - 4 MiB alloc per 4 MiB chunk).
- Entity->DTO mapping: Arc<str> interning of closed-set display fields +
  common MIMEs, 1-alloc etag/size formatting, FolderDto moves instead of
  clones. File row: 11 -> 4 allocs; folder row: 11.8 -> 1 (2.1x faster).
- CardDAV REPORT: deleted dead per-contact vCard pre-generation and the
  O(N^2) uid scan whose result was discarded (5k contacts: 55.7 -> 5.7 ms,
  9.8x); byte-identical XML asserted.
- Search-results cache: byte weigher + 32 MiB budget
  (OXICLOUD_SEARCH_CACHE_MAX_BYTES) replaces the 1000-ENTRY cap that let
  ~300 MiB of enriched rows sit in RSS; read latency parity.
- Dropped aws-config + aws-smithy-types (zero references; -82 dep-graph
  nodes, three SDK stacks gone from every build). tokio "process" is now
  an explicit feature (was enabled transitively by aws-config).

Frontend:
- Cached Intl.DateTimeFormat keyed by (locale, options) in formatDate and
  4 sibling callsites: 20k dates 2612 -> 51 ms (51.6x); vitest gate
  asserts output identity across locales and a 3x floor.

Validation: cargo fmt + clippy --all-features --all-targets -D warnings
clean; 518 unit + 548 integration-cfg tests green; new-shape endpoints
smoke-tested end-to-end over HTTP (all 5 listing sort modes with cursor
walks, WebDAV PROPFIND Depth-1, photos timeline, Basic-auth DAV login);
frontend npm run check clean, new vitest gates green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBsU2qEzny3A8WQUEuMNCr
2026-07-17 11:10:27 +00:00
Edouard Vanbelle 7aea383588 feat(antienum): 403 when sub can read, 404 otherwise
this is a UX improvement, always return a 404 not found when subject do not have any access on the resource
    but returns an explicit 403 forbidden is subject try a forbidden action on a resourse it can read

    regarding performance, the role is already in cache for the second call with read perm
2026-07-16 22:09:45 +02:00
Edouard Vanbelle 5b996bb218 security(webdav+nc): antienum (404) rather returning a 500 with reason 2026-07-16 21:25:13 +02:00
Edouard Vanbelle c1924c825b security(search): ensure that search suggenstion returns answer the user has access to 2026-07-16 21:07:18 +02:00
Dionisio Pozo 7d95a19907 Merge pull request #599 from AtalayaLabs/claude/code-performance-optimization-bq9tkf 2026-07-16 19:41:27 +02:00
Claude 82ee7da0d2 perf: serve ranges from RAM cache, stream ZIPs, overlap ingest settle, O(1) chunk gate
Round 2 of benchmark-gated optimizations (benches/ROUND2.md; every change
gated by a before/after in examples/bench_round2.rs — an AFTER that did
not beat its BEFORE was to be rolled back; none needed it):

- Range requests (REST/DAV/shares) answered from the moka content cache
  for sub-10MB files: PG resolve + open/seek/read -> Bytes::slice.
  256KiB seeks: 1,730/s -> 3.7M/s (p50 552us -> 0.15us).
- Streaming folder/share ZIPs via tokio duplex: TTFB no longer scales
  with archive size (326ms -> 0.4ms on 192MiB corpus; total also faster).
  Content-Length dropped (size unknown up front).
- NC chunked-upload per-PUT gate: O(k) directory scan+stat -> in-RAM
  per-session counter (lazy rebuild on cold start). 1,000-chunk upload
  gate cost: 33.1s -> 0.09s cumulative.
- Delta download + commit-verify now use the CDC path's
  buffered(read_prefetch) read-ahead: 64-chunk drain at 5ms open
  latency 440ms -> 51ms; order preserved.
- CDC ingest settles batches on a spawned task (depth-1 pipeline) so
  the source stream keeps flowing during PG pin + backend writes;
  rollback ledger shared + lock-serialized so compensation stays exact
  on cancellation. 512MiB paced ingest: 60-69 -> 74-75 MB/s.
  OXICLOUD_INGEST_OVERLAP=0 restores inline settling (ops/bench hatch).
- Frontend: instant-upload BLAKE3 hashing moved off the main thread to
  a bounded Web Worker pool (File handles by reference); vitest gate
  asserts the pool beats sequential (first gate draft posting buffers
  was 2.6x slower and was rewritten — copies dominated).

Validation: cargo fmt + clippy -D warnings clean; 514 unit + 544
integration tests green; 270 frontend tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBK1RdtzyP6759Muqe1K1w
2026-07-16 16:50:07 +00:00
Claude aba89c4f5d perf: eliminate N+1 hot-path queries, cache immutable lookups, stop re-compressing compressed bytes
Every change is benchmark-verified (harness + before/after numbers in
benches/, measured on this branch; reproduction commands in each doc):

DAV / sync-client hot paths
- PROPFIND dead-properties: one = ANY($1) query per 500-child page instead
  of one sequential query per child, and indexable `=` predicates instead
  of IS NOT DISTINCT FROM (seq scans). 2,000-child folder: 1.07-4.54 s of
  DB chatter -> 4-6 ms (258-773x). Applied to native + NC PROPFIND and
  both NC REPORT handlers. [benches/DEAD-PROPS.md]
- Folder paging: keyset cursor (name > $last) + new partial index
  (folder_id, name) replaces LIMIT/OFFSET full-folder rescan per page.
  Full 20k-file walk: 1266 ms -> 77 ms (16.5x). New migration
  20260917000000. [benches/PROPFIND-PAGING.md]
- NC chroot / default-drive resolution: moka caches (30 s TTL, explicit
  invalidation on drive mutations) for find_default_for_user and the
  markerless chroot FolderDto. 2 uncached queries + 2 pool checkouts per
  NC/WebDAV/WOPI request -> sub-us moka hit (p50 0.7-3.6 ms -> ~1 us).
  [benches/CHROOT-CACHE.md]
- Quota: PROPFINDs whose prop list never names a quota prop skip the
  2-query resolution entirely (wants_quota()); the remaining lookups read
  2 columns instead of the full auth.users row with its <=512 KiB avatar
  (11-16x, p50 3.4 ms -> 0.29 ms). Same narrow read now gates every
  upload quota check. [benches/QUOTA-PATH.md]

CPU on the request path
- ZIP exports (folder download, share ZIP, batch download): entries whose
  MIME says already-compressed (JPEG/MP4/zip/pdf/...) are Stored instead
  of Deflate - deflate ran inline on the tokio writer task at ~41 MB/s
  for ~0% size gain. Mixed media corpus: 4.31x wall and CPU, archive size
  unchanged. Shared predicate in common::mime_detect. [benches/ZIP-MEDIA.md]
- Compression layers: tower-http's default maps to Brotli QUALITY 11
  (verified in brotli-8.0.2 source and empirically: 90 ms per 64 KiB JSON
  response, 1.3 s per 700 KiB bundle). Both layers pinned to Precise(4):
  99x less CPU for ~15% more bytes. SPA assets are now precompressed at
  build time (scripts/precompress.mjs, 77% smaller) and served via
  ServeDir::precompressed_br/gzip: 2016x less per-request work, and
  clients get the better q11 bytes. [benches/STATIC-PRECOMPRESSED.md]

Batched / cached backend paths [benches/NPLUS1-AND-CACHES.md]
- Content-search ReBAC re-verification: new
  AuthorizationEngine::check_files_read_batch (default = old loop;
  PgAclEngine override batches drive resolution + reuses role cache).
  200 sequential point SELECTs per search -> 1-2 queries.
- Batch-ZIP subtree downloads: drop per-file re-authz + per-file Recent
  recording (2 writes/file) for subtree entries already authorized at the
  root - mirrors the native folder-download path. ~6,000 statements
  removed from a 2,000-file archive.
- CDC chunk manifests: immutable by content address, now moka-cached
  (weight-bounded 32 MiB, 60 s TTL, positive-only, invalidated on delete)
  - removes one manifest query (p50 0.44-4.4 ms) from every stream,
  range and full blob read.
- People tab: grouped COUNT + batched cover lookup instead of dragging
  every face row with its 2 KiB embedding (10k faces: 30.4 ms & 21 MB ->
  3.8 ms & 1.3 KB, 8.1x); merge() is one set-based UPDATE.
  [benches/PEOPLE-LIST.md]
- Photos timeline cursor: raw timestamptz comparison instead of
  EXTRACT(EPOCH ...) wrapper + IS NULL OR disjunction - cursor is an
  index boundary again, deep scroll stops re-scanning skipped rows.
- Public share landing: one atomic UPDATE ... access_count + 1 (was
  SELECT + full-row write-back: racy, lost updates, clobbered concurrent
  owner edits) - 3 round-trips -> 2 per visit.
- move_to_trash: dead full-entity SELECT feeding a documented no-op
  removed from both branches; dead fields dropped from TrashService.
- NFC normalization: is_nfc_quick fast path skips the decompose/recompose
  state machine for the ~100% already-NFC case (every row loaded from PG).

Frontend
- Large folders paint after page one (~200 items) via fetchFolderListing's
  new onPage hook instead of waiting for every sequential page.
- Tested-and-reverted (kept for the record): cached Intl.Collator for name
  sorts - vitest showed it 2x SLOWER than V8's argument-less localeCompare
  fast path (5.6 ms vs 12.1 ms / 5k names). Sort order untouched.

New bench harnesses under examples/ (bench feature): zip_media,
dead_props, chroot_cache, quota_path, people_list, propfind_paging,
static_precompress.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBK1RdtzyP6759Muqe1K1w
2026-07-16 14:20:20 +00:00
Dionisio Pozo b69c18b934 Merge pull request #598 from EdouardVanbelle/feat/readonly-drive 2026-07-16 07:40:29 +02:00
Dionisio Pozo c9b2daec75 Merge pull request #597 from EdouardVanbelle/fix/595-quota-issue 2026-07-16 07:40:16 +02:00
Edouard Vanbelle a6427fc028 feat(drive): add readonly policy
permmit admin to freeze a drive, trash janitor background job is also disabled for this drive
2026-07-16 01:02:15 +02:00
Edouard Vanbelle 53c301e472 fix(595): permit unlimited user quota 2026-07-15 22:21:51 +02:00
Dionisio Pozo 346e2e879c Merge pull request #594 from EdouardVanbelle/feat/caldav-read-side
fix(caldav) read side + enable caldav/cardav CI test suite
2026-07-15 09:46:11 +02:00
Edouard Vanbelle 7de83de0a9 chore(ci): wire caldav/carddav test suite 2026-07-15 07:55:31 +02:00
Edouard Vanbelle cb6c29a063 fix(caldav): fix generation of events
keep information of: ATTENDEE, ORGANIZER, CATEGORIES, STATUS, TRANSP, VALARM, X-*

    this fix answer in all calldav GET
2026-07-15 07:55:31 +02:00
Edouard Vanbelle e360d093bb test(caldav+carddav): rm xfail mark on known cases 2026-07-15 07:55:31 +02:00
Dionisio Pozo e0b9e23201 Merge pull request #591 from EdouardVanbelle/fix/caldav-time-range-parser
fix(ical): fix timerange issue
2026-07-15 07:22:14 +02:00
Dionisio Pozo fc5a3a581f Merge pull request #592 from EdouardVanbelle/fix/carddav-parser-tel-adr
fix(carddav): fix tel uri
2026-07-15 07:21:57 +02:00
Edouard Vanbelle 99d0287fec fix(test): NextcloudChunkedUploadService
fix timing issues during tests that raises:

```
thread 'infrastructure::services::nextcloud_chunked_upload_service::tests::test_chunk_paths_sorted_regardless_of_upload_order' (6962) panicked at src/infrastructure/services/nextcloud_chunked_upload_service.rs:306:9:
assertion `left == right` failed
  left: [66, 67]
 right: [65, 66, 67]
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
2026-07-14 23:38:22 +02:00
Edouard Vanbelle a67fcadeea fix(ical): fix timerange issue
accept iCal DATE-TIME + RFC 3339 fallback
2026-07-14 23:13:50 +02:00
Dionisio Pozo bad3abba27 Merge pull request #590 from EdouardVanbelle/test/caldav 2026-07-14 23:12:27 +02:00
Dionisio Pozo 4336cf97b4 Merge pull request #589 from EdouardVanbelle/fix/caldav-carddav-error-mapping 2026-07-14 23:12:11 +02:00
Dionisio Pozo a15987b1f0 Merge pull request #588 from EdouardVanbelle/fix/528-Recurrence-on-ical 2026-07-14 23:10:19 +02:00
Edouard Vanbelle dc2c8f5dcd fix(carddav): fix tel uri 2026-07-14 23:00:19 +02:00
Edouard Vanbelle ad85fd5b91 fix(caldav+carddav): return correct error rather 500 2026-07-14 22:27:30 +02:00
Edouard Vanbelle a9e05ec47c test: full caldav + carddav test suite 2026-07-14 22:01:31 +02:00
Edouard Vanbelle 7966c7178a fix(528): pass3: PUT with RECURRENCE-ID 2026-07-14 20:42:18 +02:00
Edouard Vanbelle 02f67f7a5c fix(528): pass2 add recurrence_id field 2026-07-14 20:42:18 +02:00
Edouard Vanbelle 184b9dfab6 fix(528): ical and recurrence
import use if ical and use ical::IcalParser
    prepare unit test
2026-07-14 20:42:18 +02:00
Dionisio Pozo 7557084ad3 Merge pull request #586 from EdouardVanbelle/fix/caldav+carddav-error-handling
fix(caldav+carddav) error handling
2026-07-14 15:59:34 +02:00
Edouard Vanbelle a7a45b3383 fix(caldav+carddav): raise 400 error on param issue
rather than a 500
2026-07-14 14:52:58 +02:00
Dionisio Pozo 4b98f8f0e7 Merge pull request #584 from EdouardVanbelle/doc/update
doc: update doc to reflect recent changes
2026-07-14 14:30:49 +02:00
Edouard Vanbelle 54b5b3bf4f feat(caldav+carddav): auto create default cal & card
automatically create default Calendar and default addressbook per user
    (no creation if user already have a such resource)

    default name are "Personal"

    this is using the user's life cycle like does the drives

    answers to issue #545
2026-07-14 14:30:05 +02:00
Edouard Vanbelle 5e95d6dccf doc: update doc to reflect recent changes
- grants: permission moved to roles
    - new resources (Drive, Caldav, Carddav, Playlist) now using ReBAC
    - expired shared now cleaned up
    - drive visible in Webdav
    - new login/registration options (domain allow list, policies, etc)
    - upgrade of external user into internal user
2026-07-14 13:31:48 +02:00
Dionisio Pozo 970f97b91a Merge pull request #570 from swissiety/rfc-4331-quota-properties
feat(webdav): RFC 4331 quota-available-bytes/quota-used-bytes
2026-07-14 12:58:22 +02:00
Dionisio Pozo 3ecfddd6b8 Merge pull request #583 from EdouardVanbelle/doc/app-password
doc(app password): correct doc: webdav login via app password
2026-07-14 12:58:06 +02:00
Dionisio Pozo 0832f48791 Merge pull request #582 from EdouardVanbelle/security
security(auth): specify to agents that OIDC should never be bypassed
2026-07-14 12:23:33 +02:00
Edouard Vanbelle ebb11f19c2 doc(webdav): login is via app password 2026-07-14 12:21:51 +02:00
Dionisio Pozo 2f1bea165c Merge pull request #581 from EdouardVanbelle/vitepress
chore(vitepress): convert ../ references to DioCrafts/OxiCloud links
2026-07-14 12:16:32 +02:00
Dionisio Pozo bfc7a79423 Merge pull request #580 from EdouardVanbelle/feat/user-upgrade
feat(user upgrade): permit upgrade of in invited account into internal account
2026-07-14 12:11:11 +02:00
Edouard Vanbelle f738e3f442 security(auth): specify to agents that OIDC should never be bypassed 2026-07-14 12:07:30 +02:00