feat(mobile): attachments and saved views — web/mobile parity complete - #144
Merged
Conversation
Attachments were the only feature present on web but missing on mobile. Adds expo-document-picker (~13.1.6, the SDK 53 pin), a useAttachments hook holding the upload contract, and an AttachmentList mounted in TaskDetailScreen. Upload is three steps and all three are required: presign, PUT the bytes to the returned URL, then record the row with the storage path the SERVER chose. Skipping the third leaves an object nothing references, which presents to the user as a successful upload with no attachment — so each step reports a distinct error, including record-failed for exactly that orphan case. The PUT deliberately carries no Authorization header. The signature in the query string is the authorisation and a header invalidates it. `bucket` and `uploader_id` are never sent: Hasura presets the uploader, and bucket is not client-insertable because getDownloadUrl honours it and signs with the storage root credentials. Size is checked before the presign rather than after the bytes are sent, which on cellular is a real cost. Uploads are disabled offline rather than queued, unlike comments: a presigned URL expires in 15 minutes and the picker's cache URI is not guaranteed to outlive that. TaskDetailScreen's urql mock only stubbed useQuery, so mounting a component that mutates broke all three of its tests with "useMutation is not a function". Extended rather than worked around. 383 tests pass (was 374). Coverage of the upload logic went from 21% to 91%; overall 74.5% -> 77.7%, above the 60% gate. Typecheck clean.
…l copy graphql-attachments.ts was the only file in web/src/lib carrying its own hand-written GraphQL strings. graphql-saved-views, graphql-account and graphql-ws-client all import theirs from @nself/ntask-core. That local copy is why the field names drifted: it asked for filename, size_bytes and user_id while the schema has file_name, file_size_bytes and uploader_id, so every attachment query failed validation. Fixing the names in place left the duplication that caused it, and a second copy of the same strings would drift again. Now a thin wrapper over the shared operations, matching the other three files. The presign actions come from the package too, so web and mobile issue byte-identical operations. 461 web tests pass; typecheck and build clean.
Saved views were the second and last feature present on web but missing on mobile. Views created on web are now usable on a phone. Scope note: mobile ListScreen has no filter UI at all, so there was nowhere to "apply" a view the way web does. Rather than redesign ListScreen, this mirrors the existing SmartViewScreen pattern — pick a view, see the tasks it matches, tap through to detail. Creating and editing views stays on web, where the filter UI that produces them lives. Filtering happens client-side over the tasks useSmartViews already loads, not as a GraphQL where-clause. np_saved_views.filters is a jsonb blob written by another client, so its shape is not guaranteed; building a where-clause from it risks a query that errors outright, whereas an unknown value here simply matches nothing. A view naming a deleted priority shows an empty result rather than silently falling back to everything. Undated tasks sort last in both directions — an undated task is unscheduled, not "earliest". 394 tests pass (was 383). applyFilters is covered directly, including the malformed-input cases, since it is the part that receives data it did not produce.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the two remaining web/mobile parity gaps in P5 T-07. Attachments and saved views were the only features present on web and missing on mobile.
Attachments
expo-document-picker(~13.1.6, the SDK 53 pin fromexpo/bundledNativeModules.json), auseAttachmentshook holding the upload contract, and anAttachmentListmounted inTaskDetailScreen.Upload is three steps and all three are required: presign → PUT the bytes → record the row with the storage path the server chose. Skipping the third leaves an object nothing references, which presents to the user as a successful upload with no attachment — so each step reports a distinct error, including
record-failedfor exactly that orphan case.Details that are easy to get wrong, and are commented in place:
Authorizationheader. The signature in the query string is the authorisation; a header invalidates it.bucketanduploader_idare never sent. Hasura presets the uploader, andbucketis not client-insertable becausegetDownloadUrlhonours it and signs with the storage root credentials.Saved views
Mobile
ListScreenhas no filter UI at all, so there was nowhere to "apply" a view the way web does. Rather than redesignListScreen, this mirrors the existingSmartViewScreenpattern: pick a view, see the tasks it matches, tap through to detail. Creating and editing views stays on web, where the filter UI that produces them lives.Filtering is client-side over tasks
useSmartViewsalready loads, not a GraphQL where-clause.np_saved_views.filtersis a jsonb blob written by another client, so its shape is not guaranteed — building a where-clause from it risks a query that errors outright, whereas an unknown value here simply matches nothing. A view naming a deleted priority shows an empty result rather than silently falling back to everything. Undated tasks sort last in both directions, because an undated task is unscheduled, not "earliest".A test mock that was hiding a real failure mode
TaskDetailScreen.test.tsxstubbed onlyuseQuery. Mounting a component that mutates broke all three of its tests withuseMutation is not a function. Extended rather than worked around.Verification
useAttachmentscoverageTypecheck clean; lint 0 errors (43 pre-existing warnings).
The upload logic is covered directly, including every failure branch, because a silent failure at any step is indistinguishable from success to the user.
Depends on
nself-org/packages#11 — the shared
CREATE_ATTACHMENTmust stop sendingbucket(the hardened Hasura permission rejects it), andGET_UPLOAD_URL/GET_DOWNLOAD_URLare added there rather than re-hand-written here.