Skip to content

Promote develop to main - #289

Open
lopugit wants to merge 127 commits into
mainfrom
develop
Open

Promote develop to main#289
lopugit wants to merge 127 commits into
mainfrom
develop

Conversation

@lopugit

@lopugit lopugit commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Standing promotion PR opened by the Promote develop to main workflow. Its head is develop, so every new push or merge to develop shows up here on its own. Merge it whenever main should catch up — the workflow opens the next one after the following push to develop. The Sync main into develop workflow levels develop with main again after each promotion.

📋 What this promotion carries

13 pull requests merged into develop (127 commits) will land in main when this PR merges — newest first:

PR Title Author Source branch Merged (UTC)
#293 Fix iOS drawer, camera crash, and deployment history lopugit codex/ios-drawer-camera-crash 2026-08-20
#332 Sync main into develop lopugit main 2026-08-20
#330 Reconcile canonical public/private upload scope UX lopugit codex/reconcile-canonical-upload-scopes 2026-08-20
#329 Sync main into develop lopugit main 2026-08-20
#322 fix(csp): allow the private S3 bucket origin in connect-src (uploads died on CSP-serving domains) lopugit claude/csp-allow-s3-uploads 2026-08-19
#319 feat(attachments): play every browser-playable video container inline + label sniffed types lopugit claude/screenshot-video-display-e3a72d 2026-08-19
#318 fix(ci): all-branch listener concurrency deadlock lopugit claude/all-branch-listener-concurrency-hotfix 2026-08-18
#314 feat(ci): Build all branch — thin listener + docs lopugit claude/wildcard-all-branch-auto-merge-49d963 2026-08-18
#306 feat(attachments): drag/drop sort ordering for post media & files (create + edit) lopugit claude/media-files-sort-drag-drop-1f75a0 2026-08-18
#298 Sync main into develop lopugit main 2026-08-18
#99 security: persisted-state RCE/CSP hardening + register body cap (spec 09 §B/§C/§D) lopugit claude/eval-csp-hardening 2026-08-18
#90 Scope app-data/clientId unique indexes by thingtime; 400 instead of 500 on null search conditions lopugit claude/fix-appdata-index-search-null-guard 2026-08-17
#287 Fix duplicate-SHA builds in the develop Vercel environment lopugit codex/fix-vercel-custom-env-ignore 2026-08-17
Direct commits on `develop` without a merged PR (2)
  • 07e242f5 Merge remote-tracking branch 'origin/main' into develop
  • 2322f192 Merge remote-tracking branch 'origin/main' into develop

Auto-maintained by the Promote develop to main workflow — refreshed from develop @ 2d6dfd4b (2026-08-20). Delta comments below track when entries enter or leave the promotion window.

lopugit and others added 30 commits July 21, 2026 14:08
…rruption

TODO/claude-todo/09-security-hardening.md sections C and D.

Persisted-state eval (RCE): ThingtimeProvider revived any persisted
{ttype:'function'} value via eval(code), and the replacer serialized every
function that way into localforage/IndexedDB on each state change. Anything able
to write same-origin storage (an XSS, an extension, another tab) could plant a
payload that executed on every subsequent load. Functions are now dropped on
both write and read; ThingtimeDefaults re-supplies the real ones on hydrate.

CSP: no Content-Security-Policy existed anywhere, so 'unsafe-eval' was
effectively allowed. Added a single-source policy (scripts/csp.mjs) with NO
'unsafe-eval', stamped as a header on the Vercel output (patch-vercel-output)
and the Vite dev server, and asserted by verify-vercel-output. The two inline
boot scripts in index.html moved to an external /tt-boot.js so the policy can
stay script-src 'self' without per-edit inline hashes.

Date.parse corruption: the reviver turned ANY string that passed the lenient
Date.parse ("Post 1", "2024", "March 2024") into a Date on reload, then the
replacer rewrote it as an ISO string, permanently corrupting user data after
one save/reload cycle. Revival is now restricted to strict ISO-8601 timestamps.

The codec moved to app/Providers/thingtimePersistCodec.ts (pure, React-free)
with regression tests in thingtimePersistCodec.test.ts (npm run test:persist,
6 cases: strings survive round-trips, real Dates revive, functions/hostile
payloads are dropped).

Verified: full build + verify:vercel-output pass; test:persist 6/6; app
hydrates and renders correctly under the CSP in a live browser with fonts and
Emotion styles loading unblocked. Pre-existing hydrateRoot(document) DOM errors
(TODO #1) are unchanged and reproduce identically without this change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TODO/claude-todo/09-security-hardening.md §B. Login and resend-verification
already enforce the shared config-driven limiter; register was the last auth
endpoint with neither a rate limit nor a body cap. Each signup creates a user,
burns a bcrypt hash, and queues a verification email/outbox row, so an
unbounded register is a resource-amplification + mailbox-spam vector.

- Add an `auth.register` rule to RATE_LIMIT_DEFAULTS (10 / 10 min per IP,
  admin-tunable; flows through RATE_LIMIT_ENDPOINTS + the admin panel
  automatically).
- Enforce it in the register action before any work (fail-open, like the other
  auth routes) and switch the raw `request.json()` to `readJsonBody(request,
  16 KB)` so an oversized payload is rejected 413 before it is buffered.

The `meta` mass-assignment concern in §B is already handled — the route
whitelists username/password/email/displayName and never passes `meta`.

Tests (app/tests/api/apiTests.ts): auth-register-validation now also accepts a
shape-checked 429; new auth-register-body-cap asserts an oversized body is
rejected 413.

Verified live against local Nitro: a fresh per-IP bucket returns 10x 400 then
429, and an oversized body returns 413. Full build + verify:vercel-output pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…15 follow-up)

useThingtime.tsx carried its own copy of ThingtimeTypes/EverythingTypes,
drifted from the canonical definitions in ThingtimeProvider.tsx (an
unused optional Provider field). The hook now imports and re-exports the
provider's types — one source of truth, no consumer changes (the only
external importer, ThingtimeURL.tsx, already imports from the provider;
nothing reads the dropped Provider field).

The remaining item-15 Commander V1→V2 swap is deliberately sequenced
AFTER PR #130 lands: CommanderV2 lacks the pathPrefix/placeholder props
the Thingtime.tsx inline call site depends on, and #130 is actively
rewriting CommanderV2 — porting those props now would guarantee
conflicts.

Verified: tsc --noEmit reports no errors in the touched files; /feed and
/things render live with zero console errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…98/#106)

Upgrade the persist codec from strict-ISO string revival to the tagged
scheme the duplicate TODO-9 PRs used, closing the remaining false-positive:

- Real Dates persist as {ttype:'date', iso}. The replacer reads the
  original off the holder (this[key]) because Date.toJSON converts Dates
  to strings before the replacer sees the value.
- A USER string that merely looks like a full ISO timestamp is escaped as
  {ttype:'iso-string', s} so the legacy fallback can never capture it —
  previously such a string became a Date on the next load.
- The legacy bare-ISO fallback remains only to migrate pre-tagging
  persists.
- Both replacer and reviver skip their own wrappers' inner keys (without
  the guard the escape rule re-wraps its own output unboundedly, and the
  legacy fallback hands the wrapper branches a Date instead of the
  original string — caught by the new tests).

Tests: 9/9 in test:persist, including Date + identical-looking string
coexisting, two-cycle string stability, and hostile-payload drops.
TESTING.md gains the register rate-limit checklist (from #106) and a
persisted-codec checklist. Live-verified: app hydrates under the CSP,
persists and reloads cleanly, zero console errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Conflicted paths: TESTING.md, remix/app/api/utils/rateLimit/config.ts

Resolved by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/30546808318

Co-Authored-By: Claude <noreply@anthropic.com>
Conflicted paths: TESTING.md

Resolved by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/30547496533

Co-Authored-By: Claude <noreply@anthropic.com>
POST /api/v1/auth/register was the one unauthenticated mutating auth route
with no limiter: each attempt burns a bcrypt hash, a success emails an
arbitrary address, and since PR #162 a broken-index state re-runs the
ensureIndexes battery per attempt. Throttle by IP (default 10 per 15 min,
admin-tunable, fail-open like the other auth rules) before any work runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The MongoDB connection inputs on /mongodb-status rendered as plain text —
a URL with embedded credentials (or even just an infra hostname) was
readable by anyone watching a shared screen.

- SecretInput: password-masked input with a per-field 👁/🙈 show/hide
  toggle, hidden by default; autoComplete="new-password" keeps password
  managers from offering to save or autofill; values live only in
  component state and the POST body, never in localCache.
- The connection-URL input is now a SecretInput.
- New "Individual fields" mode beside "Connection URL": scheme select
  (mongodb:// / mongodb+srv://) + user/password/host/port/database, each
  its own hidden-by-default SecretInput. The fields live-compose into the
  same `url` state, so the existing activate/save buttons work unchanged
  in both modes; user/password/database are URI-encoded, srv disables +
  clears the port, and switching modes round-trips (a pasted URL parses
  into the fields, the fields compose back).
- Inputs clear after a successful activate/save.

Verified live (worktree stack, logged-out flow): all six inputs render
type=password with individual toggles (toggle flips password↔text and
relabels Show↔Hide); fields for user "test user" / pass "p@ss:word" /
localhost:27017/tt_override_test composed byte-exact to
mongodb://test%20user:p%40ss%3Aword@localhost:27017/tt_override_test and
parsed back decoded; activation from fields mode succeeded (Custom badge,
active label, fields cleared); srv disables the port with an explanatory
placeholder; localStorage carries only host/db summaries (no mongodb://
anywhere); mobile viewport wraps with zero horizontal overflow. TESTING.md
gains three checklist lines for the new bug class.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
refactor: unify ThingtimeTypes to one source of truth (TODO 15 follow-up)
Rate-limit public registration (auth.register)
…nputs-4dcd51

Endpoint config: screen-share-safe secret inputs + individual-fields mode
Conflicted paths: remix/app/api/utils/rateLimit/config.ts, remix/app/routes/api/v1/auth/register/_register.tsx

Resolved by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/31152830723

Co-Authored-By: Claude <noreply@anthropic.com>
Conflicted paths: remix/package.json

Resolved by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/31153996937

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs: all PRs target develop, not main (AI_ALL.md publishing rule)
Preserve the canonical register IP limiter while consolidating PR #99 hardening and make sensitive conflict paths reviewer annotations instead of resolver stop conditions.
…e3a72d

feat(attachments): play every browser-playable video container inline + label sniffed types
@github-actions

Copy link
Copy Markdown
Contributor

📋 Promotion changelog updated — now carrying 8 PRs (100 commits).

Added:

The full changelog lives in the PR description.

lopugit and others added 2 commits August 19, 2026 14:41
The app CSP never allowed the direct-to-S3 multipart PUT the composer
performs, so every attachment upload died in the browser with 'The
file could not reach storage' on any surface serving the header
(pr-*.previews.dev.thingtime.com, dev, staging - and production once
the policy ships there). Bucket CORS was already correct; only the
page policy blocked the connection. connect-src now carries the exact
bucket origin derived from THINGTIME_PRIVATE_S3_BUCKET/_REGION at
build time, with a regional wildcard fallback when the env is absent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(csp): allow the private S3 bucket origin in connect-src (uploads died on CSP-serving domains)
@github-actions

Copy link
Copy Markdown
Contributor

📋 Promotion changelog updated — now carrying 9 PRs (102 commits).

Added:

The full changelog lives in the PR description.

github-actions Bot and others added 2 commits August 19, 2026 06:16
@github-actions

Copy link
Copy Markdown
Contributor

📋 Promotion changelog updated — now carrying 10 PRs (118 commits).

Added:

The full changelog lives in the PR description.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

⚠️ The resolver stopped in a later safety, verification, or publication step. No residual conflicted paths were confirmed; see the workflow run.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

⚠️ The resolver stopped in a later safety, verification, or publication step. No residual conflicted paths were confirmed; see the workflow run.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

🤝 Merged main into develop — conflicts auto-resolved by the resolve-pr-conflicts workflow.

Conflicted files:

  • graphify-out/GRAPH_REPORT.md
  • graphify-out/cache/semantic/4adb3e86d21894e6120d862d66150515a163e73e44a114f5c8ede282bf674d2e.json
  • graphify-out/cache/semantic/706ec8159e5c848da475c5b5e0b6f346cd304c49d50fbcb792aab4487e4357eb.json
  • graphify-out/cache/semantic/84230e475b26161bdcd7e75a245026998588279eeb9bcd2699b13cde500034fa.json
  • graphify-out/cache/semantic/94e4a16bf1f2effc5c42f92dd9b745b96be99ff5590fbf365bdc4792df23e96e.json
  • graphify-out/cache/semantic/e396f00630e821b189d4e1e3e751efd075116d18db92023c07a9b28dce0ca920.json
  • graphify-out/graph.json
  • graphify-out/manifest.json
  • remix/CHANGELOG.md
  • remix/scripts/verify-vercel-output.mjs

graphify-out/ was reset wholesale to the main side (repo rule: one side, never mixed — the graph merge driver is unavailable in CI).
Then re-ran graphify on the merged code WITH LLM semantic extraction (graphify extract, claude-cli backend) and committed the result — content new to this merge is semantically indexed; unchanged content came from the tracked cache.

Please review the merge commit before relying on it.

github-actions Bot and others added 4 commits August 20, 2026 15:32
Conflicted paths: graphify-out/GRAPH_REPORT.md, graphify-out/cache/semantic/4adb3e86d21894e6120d862d66150515a163e73e44a114f5c8ede282bf674d2e.json, graphify-out/cache/semantic/706ec8159e5c848da475c5b5e0b6f346cd304c49d50fbcb792aab4487e4357eb.json, graphify-out/cache/semantic/84230e475b26161bdcd7e75a245026998588279eeb9bcd2699b13cde500034fa.json, graphify-out/cache/semantic/94e4a16bf1f2effc5c42f92dd9b745b96be99ff5590fbf365bdc4792df23e96e.json, graphify-out/cache/semantic/e396f00630e821b189d4e1e3e751efd075116d18db92023c07a9b28dce0ca920.json, graphify-out/graph.json, graphify-out/manifest.json, remix/CHANGELOG.md, remix/scripts/verify-vercel-output.mjs

Resolved by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/32386080538

Co-Authored-By: Claude <noreply@anthropic.com>
`graphify extract` with LLM semantic extraction (graphify 0.9.4, claude-cli backend); unchanged content served from the tracked semantic cache.

Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/32386080538
@github-actions

Copy link
Copy Markdown
Contributor

📋 Promotion changelog updated — now carrying 11 PRs (120 commits).

Added:

The full changelog lives in the PR description.

@github-actions

Copy link
Copy Markdown
Contributor

📋 Promotion changelog updated — now carrying 12 PRs (125 commits).

Added:

The full changelog lives in the PR description.

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

🤝 Merged main into develop — conflicts auto-resolved by the resolve-pr-conflicts workflow.

Conflicted files:

  • graphify-out/GRAPH_REPORT.md
  • graphify-out/graph.json
  • graphify-out/manifest.json
  • remix/app/components/Attachments/AttachmentComposer.tsx
  • remix/app/components/Attachments/attachmentUiCore.test.ts

graphify-out/ was reset wholesale to the main side (repo rule: one side, never mixed — the graph merge driver is unavailable in CI).
Then re-ran graphify on the merged code WITH LLM semantic extraction (graphify extract, claude-cli backend) and committed the result — content new to this merge is semantically indexed; unchanged content came from the tracked cache.

Please review the merge commit before relying on it.

github-actions Bot and others added 2 commits August 20, 2026 16:46
Conflicted paths: graphify-out/GRAPH_REPORT.md, graphify-out/graph.json, graphify-out/manifest.json, remix/app/components/Attachments/AttachmentComposer.tsx, remix/app/components/Attachments/attachmentUiCore.test.ts

Resolved by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/32392536898

Co-Authored-By: Claude <noreply@anthropic.com>
`graphify extract` with LLM semantic extraction (graphify 0.9.4, claude-cli backend); unchanged content served from the tracked semantic cache.

Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/32392536898
@github-actions

Copy link
Copy Markdown
Contributor

📋 Promotion changelog updated — now carrying 13 PRs (127 commits).

Added:

The full changelog lives in the PR description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-ai-rebase Opt this PR's head branch out of AI history rewriting; the merge resolver owns its conflicts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants