# Project Qingtian / FRO Share File --- Detailed Development & Consultation Report

## 2026-09-12

## 1. Scope

Today's work concentrated on **Case 2: Client uploads files to
FRO/Qingtian and FRO stores them on the QNAP Receive File share**.

The work covered: 1. Phase 3 production activation and real-browser
upload verification. 2. Tailscale Funnel path-routing diagnosis and
correction. 3. Formal Phase 3 archival. 4. Phase 4 sequential
resumable-upload implementation. 5. Phase 4 production activation. 6. A
frontend JavaScript syntax correction found during the first browser
test. 7. A real-browser interrupted/resume test. 8. Stopping before the
final read-only Phase 4 server-side evidence check.

No Case 1 download implementation was intentionally changed.
`app/filedrop.py` remained untouched during Phase 3 and Phase 4 work
described here.

## 2. Case 2 Phase 3 --- Actual Client Upload

### 2.1 Production functionality

Phase 3 introduced the real client upload path.

Public routes: - `GET /filedrop/receive/drop/{token}` -
`POST /filedrop/receive/drop/{token}/uploads` -
`POST /filedrop/receive/drop/{token}/uploads/{upload_id}`

There is deliberately no public Receive file-list, download, or delete
route.

The Receive page supports: - multiple-file picker; - drag and drop; -
filename and size display; - Waiting / Uploading / Completed / Failed
states; - per-file progress; - aggregate progress; - duplicate-selection
suppression; - sequential uploads; - browser transmission from File
objects without loading a complete large file into JavaScript memory.

### 2.2 Phase 3 limits

Configured defaults: - maximum individual file: 100 GiB; - maximum files
per Receive link: 100; - maximum total declared bytes per link: 500
GiB; - server write chunk: 1 MiB.

Zero and negative file sizes are rejected.

### 2.3 Phase 3 storage model

Temporary upload:
`/receive-file/.fro-incoming/{drop_uuid}/{upload_uuid}.part`

Completed publication:
`/receive-file/Received/{drop_uuid}/{safe_final_filename}`

The client cannot select or supply the NAS destination path.

During upload: - writes are bounded; - token validity is repeatedly
checked; - committed offset is updated transactionally; - overflow is
rejected; - upload IDs are bound to their parent drop; - concurrent
duplicate writes are rejected; - incomplete/failed data remains in
`.fro-incoming`; - incomplete data is never represented as Completed.

Completion: - exact size is checked; - file data is flushed and `fsync`
is called; - token state is checked again; - final publication is
atomic; - completed files are never overwritten; - `Completed`,
`completed_at`, and final committed offset are recorded.

### 2.4 Phase 3 Tailscale 404 diagnosis

The first public Receive browser URL returned raw:
`{"detail":"Not Found"}`

Direct localhost access to: `/filedrop/receive/drop/{token}` returned
HTTP 200 and the branded Receive page.

Public HTTPS access reached Uvicorn as: `/receive/drop/{token}`

Root cause: the existing Tailscale mapping:
`/filedrop → http://127.0.0.1:8000` stripped the matched `/filedrop`
prefix before forwarding.

A narrowly scoped mapping was added:
`/filedrop/receive → http://127.0.0.1:8000/filedrop/receive`

Final production mapping: - `/filedrop` → `http://127.0.0.1:8000` -
`/filedrop/receive` → `http://127.0.0.1:8000/filedrop/receive`

Funnel remained enabled on HTTPS 443. Existing Case 1 `/filedrop`
behavior remained available.

### 2.5 Phase 3 real-browser verification

Test drop: `4198fea5-028f-4d33-b231-bc1de3aec02b`

Uploaded file: `2179_08192026_PUTB1164_Subang_Bistari_title_Booking.rar`

Browser result: - Completed - Overall progress 100%

NAS result: the file appeared under the corresponding
`Receive File/Received/{drop_uuid}` directory.

Final read-only verification: - upload_id:
`6bd3962a-2282-445f-9d61-756c317e0832` - expected_size: `907282` -
committed_offset: `907282` - status: `Completed` - completed_at
populated - safe final filename matched - final filesystem size:
`907282` - corresponding `.part`: absent - incoming directory: empty -
duplicate Completed rows: none - Failed/incomplete row for the upload:
none

Phase 3 classification: **COMPLETE + PRODUCTION VERIFIED + REAL BROWSER
VERIFIED**

### 2.6 Phase 3 archival

Verified checkpoints: -
`app/receive.py.checkpoint_20260912_receive_phase3_verified` -
`app/config.py.checkpoint_20260912_receive_phase3_verified` -
`templates/receive.html.checkpoint_20260912_receive_phase3_verified` -
`tests/test_receive_uploads.py.checkpoint_20260912_receive_phase3_verified` -
`tests/test_receive_links.py.checkpoint_20260912_receive_phase3_verified`

Reports created in the project report location: - `2026-09-12.md` -
`2026-09-12-detail.md`

Phase 3 final archival regression: **33/33 passed --- OK**

## 3. Case 2 Phase 4 --- Sequential Resumable Upload

### 3.1 Objective

The Phase 4 objective is to allow a large interrupted upload to continue
from the exact number of bytes already committed on the server.

The design does not use arbitrary random writes, parallel chunks,
multipart cloud storage, or deduplication.

The server is authoritative.

### 3.2 Files changed

Phase 4 implementation changed: - `app/receive.py` -
`templates/receive.html` - new `tests/test_receive_resumable.py`

Unchanged: - `app/config.py` - existing Phase 3 Receive tests -
`app/filedrop.py`

### 3.3 Phase 4 pre-change checkpoints

Created: - `app/receive.py.checkpoint_20260912_before_receive_phase4` -
`app/config.py.checkpoint_20260912_before_receive_phase4` -
`templates/receive.html.checkpoint_20260912_before_receive_phase4` -
`tests/test_receive_uploads.py.checkpoint_20260912_before_receive_phase4` -
`tests/test_receive_links.py.checkpoint_20260912_before_receive_phase4`

### 3.4 Resume protocol

New route: `GET /filedrop/receive/drop/{token}/uploads/{upload_id}`

The existing POST write route requires: - `Upload-Offset` -
`X-File-Fingerprint` - `X-File-Last-Modified` - `X-File-Size`

Server rules: - only exact authoritative `committed_offset` is
accepted; - stale offset rejected; - future offset rejected; - arbitrary
offset rejected; - `.part` size must exactly equal SQLite
`committed_offset`; - writes remain sequential and bounded; - resumed
writes append from the verified offset; - final publication still occurs
only at exact expected size; - completed files remain non-overwritable.

### 3.5 File identity

Resume is not based only on filename and size.

Identity uses: - filename; - exact file size; - browser
`lastModified`; - SHA-256 fingerprint generated from bounded first and
last 64 KiB slices plus metadata.

This avoids whole-file hashing and avoids loading a multi-gigabyte file
into browser memory.

If the local file identity does not match, the existing upload must not
be resumed.

### 3.6 Client persistence and recovery

IndexedDB stores resumable upload mapping so it can survive: -
refresh; - tab close/reopen; - browser restart, where IndexedDB
persists.

Browser security is respected: the user still re-selects the local file
when required.

Compatible unfinished uploads can be reused only within the same
token/drop.

Completed uploads are never resumed.

### 3.7 Interruption semantics

A normal disconnect/incomplete request: - retains `.part`; - retains
`committed_offset`; - retains resumable metadata/state; - does not
publish into `Received`; - does not delete valid partial data.

Terminal corruption, overflow, or publication failure remains a failure
condition.

Expired/revoked tokens cannot query or resume the upload.

### 3.8 Concurrency

Per-upload locking permits one active writer.

A competing simultaneous writer receives a conflict response rather than
corrupting the `.part` file or double-advancing the committed offset.

### 3.9 Phase 4 test results

Before Phase 4 edits: **33/33 OK**

Focused Phase 4: **12/12 OK**

Full regression: **45/45 OK**

After production restart: **45/45 OK**

### 3.10 Production activation and migration

The existing `remoteoffice` container was restarted in place: -
container ID unchanged; - running; - startup successful; - dashboard
HTTP 200; - `/receive-file` remained read/write; - `.fro-incoming` and
`Received` remained present.

SQLite migration added: - `client_last_modified` - `file_fingerprint`

SQLite `quick_check`: OK.

The prior Phase 3 Completed upload remained intact: - upload_id
`6bd3962a-2282-445f-9d61-756c317e0832` - status `Completed` -
expected_size `907282` - committed_offset `907282` - completed_at
preserved - existing filename metadata preserved - new Phase 4 identity
columns null, as expected for a pre-migration record.

### 3.11 Phase 4 frontend syntax incident

The first real-browser Phase 4 test showed that the **Choose Files**
button did nothing.

Diagnosis found a JavaScript syntax error in `templates/receive.html`
inside `send.onclick`.

Bad: `render()}}}catch(error)`

Correct: `render()}}catch(error)`

The extra `}` caused the browser to reject the entire script before
execution, so even the original file-picker event handler was never
registered.

Checkpoint:
`templates/receive.html.checkpoint_20260912_before_phase4_js_syntax_fix`

Only the unmatched brace was removed.

Verification after fix: - live JavaScript parsed successfully; - Phase 4
focused tests: **12/12 OK**; - full regression: **45/45 OK**; - Jinja
reloaded the template automatically; - no container restart was
required.

## 4. Phase 4 Real-Browser Resume Test

Temporary test Receive drop: `1336cdb8-b282-4d7b-a7d6-6b749cfea8b6`

Test file: `DJI_20251220191452_0388_D.MP4`

Displayed total size: `564.86 MB`

Test sequence: 1. Opened the production Receive URL. 2. Selected the
local MP4. 3. Began upload. 4. Upload was intentionally interrupted. 5.
Same Receive URL was reopened. 6. The exact same local file was
re-selected. 7. Browser recovered the interrupted upload.

Critical browser evidence displayed:

**Resume Available · 60.00 MB / 564.86 MB**

Overall progress: **11%**

This is important because the resumed UI did not return to zero. It
recovered a non-zero server-side upload position.

The user then clicked Upload Files and allowed the resumed upload to
complete.

## 5. What Is Proven vs. What Still Needs Final Verification

### Proven tonight

The following are directly observed or already regression-tested: -
Phase 4 is deployed in production. - Resume state is recognized after
browser interruption/reopen. - Same-file reselection recovers the
interrupted upload. - Browser shows a non-zero recovered amount: 60.00
MB. - Browser shows `Resume Available`. - Progress resumes at
approximately 11%, not 0%. - The resumed upload was subsequently allowed
to complete. - Full regression remains 45/45 after activation and after
the JS correction.

### Final evidence still required tomorrow

Do not formally archive Phase 4 until a final read-only server
verification is performed for: drop
`1336cdb8-b282-4d7b-a7d6-6b749cfea8b6` and file
`DJI_20251220191452_0388_D.MP4`.

Verify: - upload_id; - original filename; - safe final filename; -
expected_size; - committed_offset; - status; - completed_at; -
client_last_modified; - fingerprint presence; - one successful resumable
upload row; - no unexpected duplicate Completed/Failed/Cancelled
record; - final file exists under `Received/{drop_uuid}`; - final
filesystem size equals expected_size; - committed_offset equals
expected_size; - corresponding `.part` is absent; -
`.fro-incoming/{drop_uuid}` state.

Where retained application/access logs permit, also verify: - initial
partial upload; - approximately 60 MB committed before interruption; -
later request reused the same upload_id; - resumed request used a
non-zero server-authoritative `Upload-Offset`; - the upload did not
restart from byte 0; - publication occurred only after expected size was
reached.

If logs are insufficient, distinguish browser/DB/filesystem proof from
HTTP-log proof rather than inventing evidence.

## 6. Tomorrow's Starting Point

Tomorrow begin with **read-only Phase 4 final verification only**.

No new implementation should be started before this check.

If the server-side evidence is consistent, Phase 4 may then be formally
classified:

**COMPLETE + PRODUCTION VERIFIED + REAL BROWSER RESUME VERIFIED**

After that: 1. create Phase 4 verified checkpoints; 2. run final
regression; 3. archive Phase 4 reports/checkpoint state; 4. only then
discuss the next development phase.

## 7. End-of-Day Safety State

At the stopping point: - Phase 3 is formally archived and recoverable. -
Phase 4 code is deployed. - Phase 4 full regression is 45/45. -
Tailscale Funnel remains configured with the required scoped Receive
mapping. - Real-browser resume has been observed from a non-zero
point. - The resumed upload has completed from the user's perspective. -
Final server-side read-only verification is intentionally deferred until
tomorrow.
