<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>SharePoint Vault — new articles</title>
  <subtitle>Field-tested scripts, gotchas and guides for SharePoint professionals.</subtitle>
  <link href="https://sharepointvault.online/feed.xml" rel="self"/>
  <link href="https://sharepointvault.online/whats-new"/>
  <id>https://sharepointvault.online/</id>
  <updated>2026-11-14T01:41:55+01:00</updated>
  <author><name>Kamil Juřík</name></author>
  <entry>
    <title>The hosted workbench restores your web parts on the next site you open in the same tab</title>
    <link href="https://sharepointvault.online/gotchas/spfx/hosted-workbench-restores-web-parts-on-another-site"/>
    <id>https://sharepointvault.online/gotchas/spfx/hosted-workbench-restores-web-parts-on-another-site</id>
    <published>2026-11-14T01:41:55+01:00</published>
    <updated>2026-11-14T04:25:40+01:00</updated>
    <summary>The hosted workbench keeps its canvas in sessionStorage per tenant origin, not per site; open another site&apos;s workbench in the same tab and your web part boots there — with that site&apos;s pageContext, so first-run provisioning creates lists on the wrong site. Clear sp_webpart_workbench_state before switching sites.</summary>
  </entry>
  <entry>
    <title>Service health — a cross-service outage is filed under &quot;Microsoft 365 suite&quot;, not under SharePoint or Exchange</title>
    <link href="https://sharepointvault.online/gotchas/graph/service-health-suite-incidents-have-no-workload"/>
    <id>https://sharepointvault.online/gotchas/graph/service-health-suite-incidents-have-no-workload</id>
    <published>2026-11-13T23:05:53+01:00</published>
    <updated>2026-11-14T01:01:19+01:00</updated>
    <summary>Filtering serviceAnnouncement/issues by service dropped MO1550821 (12 Nov 2026, users unable to connect to multiple Microsoft 365 services) — its service was Microsoft 365 suite</summary>
  </entry>
  <entry>
    <title>Moving a static CDN off GitHub Pages without breaking the apps that load from it</title>
    <link href="https://sharepointvault.online/gotchas/tooling/moving-a-static-cdn-off-github-pages"/>
    <id>https://sharepointvault.online/gotchas/tooling/moving-a-static-cdn-off-github-pages</id>
    <published>2026-11-13T22:22:56+01:00</published>
    <updated>2026-11-13T22:22:56+01:00</updated>
    <summary>Upload git blobs, not a Windows working tree (CRLF breaks SRI); allow the new CA in CAA; missing files must stay 404, not 403; Azure static website has no CORS and Front Door ignores the query string by default; folders without a trailing slash; upload content before pointers</summary>
  </entry>
  <entry>
    <title>Creating a team right after creating its Microsoft 365 group returns 404 — retry only the 404, and don&apos;t log the failure as success</title>
    <link href="https://sharepointvault.online/gotchas/graph/create-team-on-new-group-returns-404-retry"/>
    <id>https://sharepointvault.online/gotchas/graph/create-team-on-new-group-returns-404-retry</id>
    <published>2026-11-13T19:27:03+01:00</published>
    <updated>2026-11-13T19:49:57+01:00</updated>
    <summary>PUT /groups/{id}/team fails with 404 for minutes after POST /groups (replication); retry only 404, surface a real failure as a warning</summary>
  </entry>
  <entry>
    <title>A &quot;public mode&quot; flag that disables the token check opens every endpoint in the Function App, not just the chat</title>
    <link href="https://sharepointvault.online/gotchas/azure-functions/public-mode-flag-opens-every-endpoint"/>
    <id>https://sharepointvault.online/gotchas/azure-functions/public-mode-flag-opens-every-endpoint</id>
    <published>2026-11-13T16:41:42+01:00</published>
    <updated>2026-11-13T16:58:52+01:00</updated>
    <summary>One Function App serves a signed-in SPFx app and an anonymous website chatbot; the website instance switches off the token check, and every other HTTP function in the same app (usage stats, storage read/write, file parsing) quietly becomes anonymous too — gate each non-public endpoint on the mode flag and enforce it with a test that scans every app.http registration</summary>
  </entry>
  <entry>
    <title>/me/sendMail from an account without a mailbox can time out with 504 – and the Graph SDK retries it three more times</title>
    <link href="https://sharepointvault.online/gotchas/graph/sendmail-without-mailbox-times-out-and-sdk-retries"/>
    <id>https://sharepointvault.online/gotchas/graph/sendmail-without-mailbox-times-out-and-sdk-retries</id>
    <published>2026-11-12T19:40:26+01:00</published>
    <updated>2026-11-12T19:40:26+01:00</updated>
    <summary>No MailboxNotEnabled error, just a 504 after ~30 s; the SDK RetryHandler repeats the non-idempotent POST up to 3×, so the button hangs ~2 min and a late success can send duplicates; check assignedPlans for an enabled exchange plan first</summary>
  </entry>
  <entry>
    <title>A $filter literal encoded twice finds nothing — with a 200</title>
    <link href="https://sharepointvault.online/gotchas/rest-api/filter-literal-encoded-twice-finds-nothing"/>
    <id>https://sharepointvault.online/gotchas/rest-api/filter-literal-encoded-twice-finds-nothing</id>
    <published>2026-11-12T16:51:57+01:00</published>
    <updated>2026-11-12T21:17:10+01:00</updated>
    <summary>If a helper already percent-encodes the quoted literal, wrapping the whole $filter in encodeURIComponent encodes the value a second time (/ → %252F, @ → %2540); SharePoint then compares against the escaped text and returns an empty result, no error; encode in exactly one layer and test with a fake server that decodes once and really filters</summary>
  </entry>
  <entry>
    <title>A folder created with folders/AddUsingPath in a generic list has no list item — breaking its permissions returns 404</title>
    <link href="https://sharepointvault.online/gotchas/rest-api/list-folder-via-folders-add-has-no-list-item"/>
    <id>https://sharepointvault.online/gotchas/rest-api/list-folder-via-folders-add-has-no-list-item</id>
    <published>2026-11-12T16:41:19+01:00</published>
    <updated>2026-11-12T16:55:10+01:00</updated>
    <summary>In a custom list (BaseTemplate 100) web/folders/AddUsingPath creates a &apos;naked&apos; folder: ListItemAllFields is odata.null (@odata.null under SPFx&apos;s OData v4) and breakroleinheritance is 404. Create list folders with AddValidateUpdateItemUsingPath and UnderlyingObjectType: 1, then check the item state, not just Exists</summary>
  </entry>
  <entry>
    <title>Locking down a no-script site from code: web NoCrawl is denied even to a site collection admin, and turning off access requests returns 200 and changes nothing</title>
    <link href="https://sharepointvault.online/gotchas/security/locking-down-a-noscript-site-from-code"/>
    <id>https://sharepointvault.online/gotchas/security/locking-down-a-noscript-site-from-code</id>
    <published>2026-11-12T14:22:39+01:00</published>
    <updated>2026-11-12T14:30:30+01:00</updated>
    <summary>On a modern site with custom scripts disabled, NoCrawl on the WEB returns 403 via REST and CSOM even for a site collection admin, and SetUseAccessRequestDefaultAndUpdate(false) returns 200 without effect; both work through the site settings pages, while MembersCanShare and the limited-access lockdown feature are writable via REST. Always re-read after writing a web setting.</summary>
  </entry>
  <entry>
    <title>Search REST returns 500 &quot;unknown error&quot; and 429 &quot;low resources&quot; for minutes at a time — retry the read, don&apos;t fix the query</title>
    <link href="https://sharepointvault.online/gotchas/search/search-500-and-429-are-transient-retry-reads"/>
    <id>https://sharepointvault.online/gotchas/search/search-500-and-429-are-transient-retry-reads</id>
    <published>2026-11-12T10:11:25+01:00</published>
    <updated>2026-11-12T22:49:00+01:00</updated>
    <summary>SharePoint Search REST can fail for a few minutes with HTTP 500 SearchServiceException (no Retry-After) and HTTP 429 low-resources errors; the same query succeeds seconds later, so retry reads briefly with a fixed backoff and show a truthful error with a Try again button</summary>
  </entry>
  <entry>
    <title>An ECDSA signature made in Node fails crypto.subtle.verify in the browser — Node defaults to DER, WebCrypto expects raw r||s</title>
    <link href="https://sharepointvault.online/gotchas/tooling/ecdsa-signature-node-der-vs-webcrypto-p1363"/>
    <id>https://sharepointvault.online/gotchas/tooling/ecdsa-signature-node-der-vs-webcrypto-p1363</id>
    <published>2026-11-12T01:01:25+01:00</published>
    <updated>2026-11-12T01:01:25+01:00</updated>
    <summary>crypto.sign in Node returns a DER-encoded ECDSA signature (69–72 bytes); WebCrypto verify in an SPFx bundle expects IEEE P1363 (64 bytes for P-256) and just returns false. Sign with dsaEncoding ieee-p1363, check the length, and test both directions with a DER counterexample</summary>
  </entry>
  <entry>
    <title>An SPFx web part that calls an intranet API makes Chrome and Edge ask every user for local network access</title>
    <link href="https://sharepointvault.online/gotchas/spfx/spfx-calling-an-intranet-api-hits-local-network-access"/>
    <id>https://sharepointvault.online/gotchas/spfx/spfx-calling-an-intranet-api-hits-local-network-access</id>
    <published>2026-11-12T00:00:52+01:00</published>
    <updated>2026-11-12T00:00:52+01:00</updated>
    <summary>A *.sharepoint.com page fetching a host that resolves to a private IP is a local network request – Chrome (from 142) and Edge prompt every user and a refusal is a bare Failed to fetch; pre-allow the SharePoint origin by policy and still plan for CORS, trusted HTTPS, VPN-only reach and per-user tokens – or let a connector inside the network write to a list through Sites.Selected</summary>
  </entry>
  <entry>
    <title>Two &quot;Uncaught (in promise)&quot; from sp-pages-assembly on a modern page are SharePoint&apos;s own, not your web part</title>
    <link href="https://sharepointvault.online/gotchas/spfx/sp-pages-assembly-uncaught-in-promise-noise"/>
    <id>https://sharepointvault.online/gotchas/spfx/sp-pages-assembly-uncaught-in-promise-noise</id>
    <published>2026-11-11T23:40:02+01:00</published>
    <updated>2026-11-11T23:50:50+01:00</updated>
    <summary>On SharePoint Online modern pages the console can show two &quot;Uncaught (in promise)&quot; exceptions with reason undefined and a stack only inside sp-pages-assembly, on every load and on SPA navigation – they also appear on a page with no custom code at all, so prove it with an A/B against such a page before you debug your own loader or web part; a page whose application customizer hides the SharePoint chrome may not show them, which makes &quot;only some of my apps have it&quot; look like a bug in those apps</summary>
  </entry>
  <entry>
    <title>Retrying a write without the &quot;new&quot; columns on any 400 silently loses data — drop only the column the error names</title>
    <link href="https://sharepointvault.online/gotchas/rest-api/retry-without-new-columns-on-any-400-loses-data"/>
    <id>https://sharepointvault.online/gotchas/rest-api/retry-without-new-columns-on-any-400-loses-data</id>
    <published>2026-11-11T21:31:40+01:00</published>
    <updated>2026-11-11T22:52:16+01:00</updated>
    <summary>When one new column is missing, a group-wide retry drops the ones that exist too; drop only the fields the error names as not existing, loop, and tell the user what was left out</summary>
  </entry>
  <entry>
    <title>Manifest SRI does not cover scripts your SPFx bundle loads itself (pdf.js, tesseract.js, workers)</title>
    <link href="https://sharepointvault.online/gotchas/spfx/spfx-sri-scripts-your-bundle-loads-itself"/>
    <id>https://sharepointvault.online/gotchas/spfx/spfx-sri-scripts-your-bundle-loads-itself</id>
    <published>2026-11-11T20:46:19+01:00</published>
    <updated>2026-11-11T22:01:24+01:00</updated>
    <summary>Pinning the loader in the .sppkg and giving every manifest path and webpack chunk an integrity hash still leaves a hole – third-party libraries your own code injects with a script tag, fetches for a worker or that a worker pulls in with importScripts run unverified; keep their SRI hashes in the already verified bundle, set the integrity attribute (not the property), fetch worker code with integrity into a blob, and for tesseract.js 5 concatenate core and worker into one blob so importScripts is skipped</summary>
  </entry>
  <entry>
    <title>Power Platform admin API returns nextLink on a regional host – a guard that only knows api.powerapps.com breaks paging for US tenants</title>
    <link href="https://sharepointvault.online/gotchas/rest-api/power-platform-admin-api-regional-nextlink"/>
    <id>https://sharepointvault.online/gotchas/rest-api/power-platform-admin-api-regional-nextlink</id>
    <published>2026-11-11T16:52:17+01:00</published>
    <updated>2026-11-11T16:52:17+01:00</updated>
    <summary>Admin listings page through unitedstates.api.powerapps.com (and other one-label regional hosts); a token guard that allows only the global host rejects page two, and you never see it on a small tenant – allow exactly one DNS label in front of the three admin hosts, nothing else</summary>
  </entry>
  <entry>
    <title>Manage Lists lets you read other people&apos;s items on a WriteSecurity 2 list, but not edit them — that takes Override List Behaviors</title>
    <link href="https://sharepointvault.online/gotchas/permissions/editing-others-items-needs-override-list-behaviors"/>
    <id>https://sharepointvault.online/gotchas/permissions/editing-others-items-needs-override-list-behaviors</id>
    <published>2026-11-11T16:05:34+01:00</published>
    <updated>2026-11-11T16:05:34+01:00</updated>
    <summary>On a list with item-level write security, the Edit role (Manage Lists) still gets 403 when it updates an item someone else created; only a role with Override List Behaviors (CancelCheckout, Low 0x100) gets through, so moving a group from Full Control to Edit silently takes that ability away</summary>
  </entry>
  <entry>
    <title>Load an SPFx bundle&apos;s lazy chunk from the browser console — no clicking through the UI</title>
    <link href="https://sharepointvault.online/gotchas/spfx/load-a-lazy-chunk-from-the-console-via-the-webpack-runtime"/>
    <id>https://sharepointvault.online/gotchas/spfx/load-a-lazy-chunk-from-the-console-via-the-webpack-runtime</id>
    <published>2026-11-11T08:48:09+01:00</published>
    <updated>2026-11-11T08:51:06+01:00</updated>
    <summary>Push a fake chunk with a runtime callback into the bundle&apos;s webpackJsonp_ array to get its real __webpack_require__, then req.e(chunkId) loads the chunk exactly as the app would — for checking 404s, SRI integrity or CSP on features you would rather not trigger</summary>
  </entry>
  <entry>
    <title>Renaming your SharePoint domain (contosoltd.sharepoint.com to contoso.sharepoint.com)</title>
    <link href="https://sharepointvault.online/guides/renaming-your-sharepoint-domain"/>
    <id>https://sharepointvault.online/guides/renaming-your-sharepoint-domain</id>
    <published>2026-11-10T18:36:53+01:00</published>
    <updated>2026-11-10T18:42:57+01:00</updated>
    <summary>Scheduling a tenant rename on a fresh tenant – checking the name is free, adding the matching .onmicrosoft.com domain the right way, the 24-hour scheduling floor (error -772), what -WhatIf and the status output look like, and what to freeze until it finishes</summary>
  </entry>
  <entry>
    <title>List item versions come back newest-first and include the current one – the opposite of file versions</title>
    <link href="https://sharepointvault.online/gotchas/rest-api/list-item-versions-are-newest-first-and-include-current"/>
    <id>https://sharepointvault.online/gotchas/rest-api/list-item-versions-are-newest-first-and-include-current</id>
    <published>2026-11-10T16:31:43+01:00</published>
    <updated>2026-11-10T16:34:08+01:00</updated>
    <summary>items(id)/versions is DESCENDING and its first row is the current version (IsCurrentVersion: true) – the opposite of file /Versions; Created on a version is UTC WITHOUT the Z, so new Date() shifts it by the browser offset – use Modified; VersionId = major*512 + minor</summary>
  </entry>
  <entry>
    <title>A Teams message your app posts in the user&apos;s name doesn&apos;t notify that user — and &quot;I can&apos;t see it&quot; needs a read-back, not a guess</title>
    <link href="https://sharepointvault.online/gotchas/graph/teams-message-sent-as-user-does-not-notify-sender"/>
    <id>https://sharepointvault.online/gotchas/graph/teams-message-sent-as-user-does-not-notify-sender</id>
    <published>2026-11-10T04:51:48+01:00</published>
    <updated>2026-11-10T04:53:34+01:00</updated>
    <summary>Delegated ChatMessage.Send delivers (even to the self chat 48:notes), but Teams never alerts the sender; verify with GET + webUrl, notify from the app</summary>
  </entry>
  <entry>
    <title>SPFx adds Subresource Integrity to a script when the manifest path is { path, integrity }</title>
    <link href="https://sharepointvault.online/gotchas/spfx/spfx-sri-integrity-via-manifest-path"/>
    <id>https://sharepointvault.online/gotchas/spfx/spfx-sri-integrity-via-manifest-path</id>
    <published>2026-11-10T04:48:36+01:00</published>
    <updated>2026-11-11T23:00:25+01:00</updated>
    <summary>A component manifest you pass to SPComponentLoader.loadComponent can carry an SRI hash per script resource as path { path, integrity }; SharePoint then emits script integrity crossorigin=anonymous and the browser refuses tampered bytes – pin the bootstrap the same way from the .sppkg manifest (immutable CDN file), because includeClientSideAssets puts it in ClientSideAssets that guests cannot read; plus three traps that make console tests lie (id+version cache, requirejs path cache, CRLF in the git working copy); on list and library views SharePoint applies none of it, so a ListView Command Set or Application Customizer there runs unverified by the browser</summary>
  </entry>
  <entry>
    <title>Console scripts on SharePoint hang because the ODSP service worker stalls every fetch</title>
    <link href="https://sharepointvault.online/gotchas/tooling/console-fetch-hangs-under-odsp-service-worker"/>
    <id>https://sharepointvault.online/gotchas/tooling/console-fetch-hangs-under-odsp-service-worker</id>
    <published>2026-11-09T21:23:20+01:00</published>
    <updated>2026-11-09T21:24:38+01:00</updated>
    <summary>A SharePoint page is controlled by the odspserviceworkerproxy.aspx service worker; when it gets stuck, every fetch from the page (even to another domain) never resolves while timers still run – a hard reload (Shift+F5) loads the page outside the worker and the script works</summary>
  </entry>
  <entry>
    <title>A trailing-whitespace regex freezes the page when it parses AI output</title>
    <link href="https://sharepointvault.online/gotchas/spfx/trailing-whitespace-regex-is-quadratic-on-ai-output"/>
    <id>https://sharepointvault.online/gotchas/spfx/trailing-whitespace-regex-is-quadratic-on-ai-output</id>
    <published>2026-11-08T18:19:49+01:00</published>
    <updated>2026-11-08T18:20:24+01:00</updated>
    <summary>/\s+$/ and lazy (.+?)\s*$ are quadratic on a line with a long run of spaces that does not end the line; text from an AI model or a foreign document is untrusted input, so trim with trimEnd() and anchor captures on a non-space character</summary>
  </entry>
  <entry>
    <title>A textarea jumps after you change its text from code and restore the selection</title>
    <link href="https://sharepointvault.online/gotchas/spfx/textarea-jumps-after-programmatic-selection"/>
    <id>https://sharepointvault.online/gotchas/spfx/textarea-jumps-after-programmatic-selection</id>
    <published>2026-11-08T12:00:38+01:00</published>
    <updated>2026-11-08T12:19:38+01:00</updated>
    <summary>focus() scrolls the page to the element and, in Chromium, setSelectionRange scrolls the textarea to the end of the selection; restore the selection with focus({ preventScroll }) and put scrollTop back</summary>
  </entry>
  <entry>
    <title>Two MERGEs to the same list item at once: the second one fails with 409 Save Conflict even with IF-MATCH: * – and a fire-and-forget write just disappears</title>
    <link href="https://sharepointvault.online/gotchas/rest-api/concurrent-merge-same-item-409-save-conflict"/>
    <id>https://sharepointvault.online/gotchas/rest-api/concurrent-merge-same-item-409-save-conflict</id>
    <published>2026-11-08T02:26:43+01:00</published>
    <updated>2026-11-08T02:26:43+01:00</updated>
    <summary>IF-MATCH: * only switches off the ETag check (412); SharePoint still refuses a second save of the same item while the first is in flight with 409 Save Conflict. Queue writes per item (list#id), retry 409 briefly, never retry 412, and do not let a state-carrying write fail silently</summary>
  </entry>
  <entry>
    <title>Restoring inheritance is a one-way door — snapshot the unique assignments first</title>
    <link href="https://sharepointvault.online/gotchas/permissions/reset-inheritance-is-a-one-way-door"/>
    <id>https://sharepointvault.online/gotchas/permissions/reset-inheritance-is-a-one-way-door</id>
    <published>2026-11-07T13:41:50+01:00</published>
    <updated>2026-11-07T13:41:50+01:00</updated>
    <summary>resetroleinheritance throws away every unique role assignment and SharePoint keeps no copy, so an &quot;undo&quot; is only possible if the code saved the assignments BEFORE the call; re-breaking with copyRoleAssignments=true gives you the parent&apos;s ACL, not the old one, so the undo has to reconcile (add missing, remove extra) against the snapshot — keyed by principal id and role definition NAME, skipping Limited Access</summary>
  </entry>
  <entry>
    <title>StorageMetrics lies two ways — all zeros, or plausible numbers from an older state</title>
    <link href="https://sharepointvault.online/gotchas/rest-api/storagemetrics-can-be-stale-not-just-zero"/>
    <id>https://sharepointvault.online/gotchas/rest-api/storagemetrics-can-be-stale-not-just-zero</id>
    <published>2026-11-06T19:54:42+01:00</published>
    <updated>2026-11-06T19:55:46+01:00</updated>
    <summary>zero = not calculated, but non-zero can be stale (261 files / 3.1 GB vs real 794 / 9.5 GB); trust it only when TotalFileCount exactly matches the files you counted</summary>
  </entry>
  <entry>
    <title>accountEnabled eq false is not a list of leavers — blocked shared mailboxes are in it too</title>
    <link href="https://sharepointvault.online/gotchas/graph/disabled-accounts-include-shared-mailboxes"/>
    <id>https://sharepointvault.online/gotchas/graph/disabled-accounts-include-shared-mailboxes</id>
    <published>2026-11-06T19:16:37+01:00</published>
    <updated>2026-11-06T19:21:00+01:00</updated>
    <summary>blocked members ≠ former employees; tell shared mailboxes apart via the mailbox usage report&apos;s recipientType (needs real names) or mailboxSettings.userPurpose (per user)</summary>
  </entry>
  <entry>
    <title>Purview audit query API — a query for a service you have no scope for still &quot;succeeds&quot; with 0 records</title>
    <link href="https://sharepointvault.online/gotchas/graph/audit-query-silently-filters-by-scope"/>
    <id>https://sharepointvault.online/gotchas/graph/audit-query-silently-filters-by-scope</id>
    <published>2026-11-06T14:05:06+01:00</published>
    <updated>2026-11-06T15:09:02+01:00</updated>
    <summary>With only AuditLogsQuery-SharePoint.Read.All, Exchange and Entra queries return 201 → succeeded → 0 records; an unfiltered query returns only SharePoint records</summary>
  </entry>
  <entry>
    <title>Exchange Online Admin API (the REST behind EXO V3) cannot be called from the browser — no CORS, and SPFx tokens lack Exchange.Manage</title>
    <link href="https://sharepointvault.online/gotchas/graph/exchange-admin-api-no-cors-from-browser"/>
    <id>https://sharepointvault.online/gotchas/graph/exchange-admin-api-no-cors-from-browser</id>
    <published>2026-11-06T14:05:06+01:00</published>
    <updated>2026-11-06T14:05:06+01:00</updated>
    <summary>adminapi/beta/{tenant}/InvokeCommand fails with &apos;Failed to fetch&apos; even with a fake token; read Exchange config with an admin-run script and import, or use Graph message trace and mailbox reports</summary>
  </entry>
  <entry>
    <title>A shared stylesheet injected once per page keeps the version that rendered first — your CSS fix may not apply next to an older build</title>
    <link href="https://sharepointvault.online/gotchas/spfx/shared-style-tag-first-web-part-wins"/>
    <id>https://sharepointvault.online/gotchas/spfx/shared-style-tag-first-web-part-wins</id>
    <published>2026-11-05T12:57:10+01:00</published>
    <updated>2026-11-05T13:04:52+01:00</updated>
    <summary>A library that injects only if the id is missing lets whichever bundle renders first decide the CSS for every web part on the page, so a fixed rule in a newer bundle can silently lose to an older copy that rendered first</summary>
  </entry>
  <entry>
    <title>Jest&apos;s 5-second default timeout fails render tests under load, and the timed-out test breaks the next one</title>
    <link href="https://sharepointvault.online/gotchas/tooling/jest-default-timeout-under-load-breaks-the-next-test"/>
    <id>https://sharepointvault.online/gotchas/tooling/jest-default-timeout-under-load-breaks-the-next-test</id>
    <published>2026-11-05T10:54:27+01:00</published>
    <updated>2026-11-05T11:06:22+01:00</updated>
    <summary>The first jsdom + React test of a suite pays for the cold module load; on a busy build machine it passes 5 s, Jest abandons it while its async render keeps running, and the following test fails with &quot;button not found&quot; or a doubled write. Set testTimeout for the project, not per test</summary>
  </entry>
  <entry>
    <title>window.scrollTo does nothing in an SPFx web part on a modern page</title>
    <link href="https://sharepointvault.online/gotchas/spfx/window-scrollto-does-nothing-on-modern-page"/>
    <id>https://sharepointvault.online/gotchas/spfx/window-scrollto-does-nothing-on-modern-page</id>
    <published>2026-11-05T05:53:06+01:00</published>
    <updated>2026-11-05T05:58:50+01:00</updated>
    <summary>The page scrolls inside a canvas element, not the window; switching views in a single-page web part leaves the user mid-page. Find the real scrolling ancestor and reset that</summary>
  </entry>
  <entry>
    <title>A user with Read on one page only is denied at the site root URL — link to the page, not the site</title>
    <link href="https://sharepointvault.online/gotchas/permissions/page-only-reader-is-denied-at-the-site-root"/>
    <id>https://sharepointvault.online/gotchas/permissions/page-only-reader-is-denied-at-the-site-root</id>
    <published>2026-11-04T15:03:36+01:00</published>
    <updated>2026-11-04T15:03:36+01:00</updated>
    <summary>When a principal has Read on a single page in Site Pages and only Limited Access on the site, opening the site URL ends on AccessDenied.aspx even though the page itself opens fine — so a link that falls back to the site root breaks exactly for the narrow audience it was meant for</summary>
  </entry>
  <entry>
    <title>Root site swap refuses hub sites, and any site Publishing ever touched</title>
    <link href="https://sharepointvault.online/gotchas/tooling/site-swap-refuses-hub-sites-and-ever-published-sites"/>
    <id>https://sharepointvault.online/gotchas/tooling/site-swap-refuses-hub-sites-and-ever-published-sites</id>
    <published>2026-11-04T11:41:25+01:00</published>
    <updated>2026-11-04T12:02:14+01:00</updated>
    <summary>Invoke-SPOSiteSwap rejects a source or target that is a hub site or associated with one, and rejects a source where the Publishing feature was ever activated — not just currently; unregistering a hub leaves children appearing associated for up to an hour, so remove the associations explicitly before the swap, and never activate Publishing on the site you intend to promote</summary>
  </entry>
  <entry>
    <title>Narrowing a principal to Read — &quot;already reads&quot; checked before stripping write leaves it with nothing</title>
    <link href="https://sharepointvault.online/gotchas/permissions/narrowing-to-read-checks-view-before-stripping-write"/>
    <id>https://sharepointvault.online/gotchas/permissions/narrowing-to-read-checks-view-before-stripping-write</id>
    <published>2026-11-03T21:28:11+01:00</published>
    <updated>2026-11-03T21:33:55+01:00</updated>
    <summary>Edit and Contribute contain the View bit, so a &quot;does it already read?&quot; check passes, Read is never added, and the step that removes the write binding then removes the only binding that carried read access — the principal ends with no access and the routine reports unreconciled forever</summary>
  </entry>
  <entry>
    <title>/_api/hubsites is trimmed per user — a lookup that walks the hubs finds your app for the page editor and not for the reader</title>
    <link href="https://sharepointvault.online/gotchas/rest-api/hubsites-list-is-trimmed-per-user"/>
    <id>https://sharepointvault.online/gotchas/rest-api/hubsites-list-is-trimmed-per-user</id>
    <published>2026-11-03T14:43:20+01:00</published>
    <updated>2026-11-03T14:43:20+01:00</updated>
    <summary>GET /_api/hubsites returned 2 hubs to an admin and 1 to an ordinary user who had Full Control on the missing hub&apos;s site, so client code that discovers something by walking the hub list works for the editor and silently fails for readers; resolve it at authoring time and store the result in the web part properties</summary>
  </entry>
  <entry>
    <title>Scope SharePoint Search to hundreds or thousands of sites with RefinementFilters — and or() with one value returns nothing</title>
    <link href="https://sharepointvault.online/gotchas/search/scope-search-to-a-list-of-sites-refinementfilters"/>
    <id>https://sharepointvault.online/gotchas/search/scope-search-to-a-list-of-sites-refinementfilters</id>
    <published>2026-11-03T13:42:32+01:00</published>
    <updated>2026-11-03T13:44:10+01:00</updated>
    <summary>To restrict any Search query to an explicit list of site collections, POST to /_api/search/postquery with RefinementFilters SPSiteUrl:or(...) (tested with 3,000 URLs); a single value must use equals() because or() with one value returns zero rows; and LastModifiedTime of an STS_Site row says nothing about activity on the site</summary>
  </entry>
  <entry>
    <title>A file&apos;s time is not its item&apos;s time – a copy job carries the item time, and ListItemAllFields returns dates in the web&apos;s zone without &quot;Z&quot;</title>
    <link href="https://sharepointvault.online/gotchas/rest-api/listitemallfields-dates-are-web-local-without-z"/>
    <id>https://sharepointvault.online/gotchas/rest-api/listitemallfields-dates-are-web-local-without-z</id>
    <published>2026-11-01T19:16:00+01:00</published>
    <updated>2026-11-01T19:17:16+01:00</updated>
    <summary>TimeCreated/TimeLastModified of a file can differ from the item&apos;s Created/Modified by seconds or years; copy jobs carry the item time, so compare items – and read them from /items(id) (UTC), because ListItemAllFields returns web-local time without Z</summary>
  </entry>
</feed>
