@amityco/social-plus-vise 0.11.0 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -963,7 +963,7 @@
963
963
  {
964
964
  "id": "android.session.renewal",
965
965
  "version": 1,
966
- "title": "Android login must pass an AmitySessionHandler that renews tokens",
966
+ "title": "Android login must pass a SessionHandler that renews tokens",
967
967
  "severity": "warning",
968
968
  "rationale": "Access tokens expire in production. Without a session handler that calls renewal.renew() in sessionWillRenewAccessToken, the SDK cannot refresh sessions and customers see silent auth failures.",
969
969
  "applies_when": {
@@ -992,7 +992,7 @@
992
992
  "evidence_required": [
993
993
  {
994
994
  "field": "session_handler",
995
- "description": "Where AmitySessionHandler.sessionWillRenewAccessToken implementation lives.",
995
+ "description": "Where the SessionHandler.sessionWillRenewAccessToken implementation lives.",
996
996
  "upload_policy": "upload-with-consent"
997
997
  }
998
998
  ]
@@ -1007,7 +1007,7 @@
1007
1007
  {
1008
1008
  "id": "flutter.session.renewal",
1009
1009
  "version": 1,
1010
- "title": "Flutter login must pass an AmitySessionHandler that renews tokens",
1010
+ "title": "Flutter login must pass a session handler callback (AccessTokenRenewal renewal) that renews tokens",
1011
1011
  "severity": "warning",
1012
1012
  "rationale": "Access tokens expire in production. Without a session handler that calls renewal.renew() in sessionWillRenewAccessToken, the SDK cannot refresh sessions and customers see silent auth failures.",
1013
1013
  "applies_when": {
@@ -1051,9 +1051,9 @@
1051
1051
  {
1052
1052
  "id": "ios.session.renewal",
1053
1053
  "version": 1,
1054
- "title": "iOS login must pass an AmitySessionHandler that renews tokens",
1054
+ "title": "iOS login must pass a SessionHandler that renews tokens",
1055
1055
  "severity": "warning",
1056
- "rationale": "Access tokens expire in production. Without an AmitySessionHandler that calls renewal.renew() in sessionWillRenewAccessToken, the iOS SDK cannot refresh sessions and customers see silent auth failures.",
1056
+ "rationale": "Access tokens expire in production. Without a SessionHandler that calls renewal.renew() in sessionWillRenewAccessToken, the iOS SDK cannot refresh sessions and customers see silent auth failures.",
1057
1057
  "applies_when": {
1058
1058
  "platforms": [
1059
1059
  "ios"
@@ -1080,7 +1080,7 @@
1080
1080
  "evidence_required": [
1081
1081
  {
1082
1082
  "field": "session_handler",
1083
- "description": "Where AmitySessionHandler.sessionWillRenewAccessToken implementation lives.",
1083
+ "description": "Where the SessionHandler.sessionWillRenewAccessToken implementation lives.",
1084
1084
  "upload_policy": "upload-with-consent"
1085
1085
  }
1086
1086
  ]
@@ -1380,7 +1380,7 @@
1380
1380
  "version": 1,
1381
1381
  "title": "Android session handler must be retained",
1382
1382
  "severity": "warning",
1383
- "rationale": "AmitySessionHandler declared as a function-local variable gets garbage collected. The SDK's renewal callback will then call into nothing, causing sessions to silently expire.",
1383
+ "rationale": "A SessionHandler declared as a function-local variable gets garbage collected. The SDK's renewal callback will then call into nothing, causing sessions to silently expire.",
1384
1384
  "applies_when": {
1385
1385
  "platforms": [
1386
1386
  "android"
@@ -1416,7 +1416,7 @@
1416
1416
  "version": 1,
1417
1417
  "title": "Flutter session handler must be retained",
1418
1418
  "severity": "warning",
1419
- "rationale": "AmitySessionHandler declared as a function-local variable gets garbage collected by Dart. The SDK's renewal callback will then call into nothing, causing sessions to silently expire.",
1419
+ "rationale": "A session handler callback held only as a function-local variable gets garbage collected by Dart. The SDK's renewal callback will then call into nothing, causing sessions to silently expire.",
1420
1420
  "applies_when": {
1421
1421
  "platforms": [
1422
1422
  "flutter"
@@ -1452,7 +1452,7 @@
1452
1452
  "version": 1,
1453
1453
  "title": "iOS session handler must be retained",
1454
1454
  "severity": "warning",
1455
- "rationale": "AmitySessionHandler declared as a function-local variable will be released by ARC. The SDK's renewal callback will then call into nothing, causing sessions to silently expire.",
1455
+ "rationale": "A SessionHandler declared as a function-local variable will be released by ARC. The SDK's renewal callback will then call into nothing, causing sessions to silently expire.",
1456
1456
  "applies_when": {
1457
1457
  "platforms": [
1458
1458
  "ios"
@@ -35,63 +35,17 @@ vise validate .
35
35
  vise run-sensors .
36
36
  ```
37
37
 
38
+ **`vise check .` is mandatory, not optional. You are not done until it passes or every finding is explicitly attested.** Running `vise plan` and `vise inspect` but skipping `vise check` is the most common failure mode — it means the deterministic catch-net never ran and known-bad patterns ship. When you read a `vise plan`, do not truncate it (`| head` drops the implementation steps); read the full `implementationSteps` array.
39
+
38
40
  Treat Vise runtime smoke sensors as real validation. For TypeScript/React Native projects, `vise run-sensors` may include `TypeScript SDK import smoke`; if it fails, the SDK package does not resolve from the host project runtime and the integration is not done.
39
41
 
40
42
  In CI or pull-request pipelines, use `vise check . --ci`. It is read-only, exits non-zero unless compliance is green, and never writes deterministic-pass records; use `vise sync` only during the implementation loop after a local green check.
41
43
 
42
- ## Debugging & Troubleshooting (Day 2 Operations)
43
-
44
- If the customer reports an SDK-specific runtime crash, build failure, or logical error (e.g. data not loading), start with `vise debug` before broad repo inspection or sidecar exploration.
45
-
46
- **CRITICAL SCOPE LIMIT:** Do NOT spam `vise debug` for generic UI/CSS errors or non-SDK issues. ONLY call `vise debug` if the error explicitly contains keywords like `social.plus`, `social-plus`, `Amity`, `live-collection`, or if the error occurs in a file that imports `social.plus` packages.
47
-
48
- For the first repair pass, prefer the compact troubleshooting loop:
49
-
50
- ```bash
51
- vise debug . --error-file "logs/crash.log" --brief
52
- vise check . --ci
53
- vise run-sensors .
54
- ```
55
-
56
- When debugging, pass the stack trace to Vise:
57
-
58
- ```bash
59
- vise debug . --error "stack trace or exception message here"
60
- ```
61
- Or, if the error is in a file:
62
- ```bash
63
- vise debug . --error-file "logs/crash.log"
64
- ```
65
- Or, for a compact repair brief:
66
- ```bash
67
- vise debug . --error-file "logs/crash.log" --brief
68
- ```
69
-
70
- The output contains `likelyCause`, `correlatedRules`, and a compact `repairBrief` with:
44
+ ## Debugging (Day 2 Operations)
71
45
 
72
- - the primary rule to repair first
73
- - likely file hints
74
- - minimum patch shape
75
- - invariants to preserve
76
- - verification commands
46
+ If the customer reports an **SDK-specific** runtime crash, build failure, or data-not-loading bug, start with `vise debug .` (with `--error` / `--error-file`) **before** broad repo inspection — do not guess the fix first. Scope limit: only for errors mentioning `social.plus`/`Amity`/`live-collection` or in files importing the SDK; never for generic UI/CSS issues. Full `vise debug` workflow and `repairBrief` format: **see `reference/debugging.md`**.
77
47
 
78
- Do not guess the fix before consulting `vise debug`. Do not read broad `sp-vise/` history on the first pass unless the `repairBrief` is ambiguous or the first repair fails.
79
-
80
- ## Engagement Scope (Optional)
81
-
82
- If the customer has set up `sp-vise/engagement.json` (via `vise engagement init`), respect the declared scope. `vise engagement show .` prints the engagement metadata: tier, customer id, contracted outcomes, reviewer assignment.
83
-
84
- When `vise init` reports a `warnings` array containing "not in the engagement scope", surface that to the user — don't silently widen scope. The user either extends the scope by editing `engagement.json` or accepts that the current work is out-of-contract.
85
-
86
- If no `engagement.json` exists, do not create one without the user's intent. `vise engagement init` is a contractual artifact; agents should only run it when the user explicitly asks to set up engagement metadata, typically with `--tier`, `--customer-id`, and `--scope`.
87
-
88
- If `vise` is not installed, use:
89
-
90
- ```sh
91
- npm install -g @amityco/social-plus-vise
92
- ```
93
-
94
- Then retry the `vise` command.
48
+ (If `vise` isn't installed: `npm install -g @amityco/social-plus-vise`. Engagement-scope handling `sp-vise/engagement.json` is in `reference/operations.md`.)
95
49
 
96
50
  ## Stop Instead Of Guessing
97
51
 
@@ -175,6 +129,15 @@ Amity exposes a global `unreadCount` stream that syncs across devices.
175
129
  - **Correct pattern**: Subscribe to the unread stream provided by the SDK (e.g. `AmityCoreClient.unreadCount()`, `getUnreadCount()`, `unread.stream`).
176
130
  - **Wrong pattern**: Hand-rolling unread counting on the client (e.g. `posts.filter(p => !p.isRead).length`). This causes counts to go out of sync because the server's read state isn't pushed to the manual counter.
177
131
 
132
+ A read *count* with no way to clear it is half a feature: when a channel/thread is opened, mark it read so the server-side count actually decrements (`channel.markAsRead()` / `message.markRead()`, or `startMessageReceiptSync` + `stopMessageReceiptSync` paired with the view lifecycle). Reading the unread stream but never marking read leaves the badge stuck.
133
+
134
+ ### Message Delivery State
135
+
136
+ A sent message starts in a `syncing` state and can land in `error` (e.g. network failure, or after an SDK session restart). A composer that renders only the optimistic success leaves failed messages silently stuck.
137
+
138
+ - **Correct pattern**: Observe each message's `syncState` and surface failed sends with a retry/delete affordance; clean up unrecoverable failures (e.g. `deleteFailedMessages()`) on init.
139
+ - **Wrong pattern**: Calling `createMessage` and rendering success unconditionally, with no handling for the `error` syncState.
140
+
178
141
  ### File Upload & Media
179
142
 
180
143
  Uploading media to external buckets (S3, Cloudinary) directly and trying to attach the resulting URL to an Amity post will fail. The SDK won't recognize external URLs as attachments.
@@ -202,15 +165,36 @@ When using a LiveCollection or live object, also handle connection-state changes
202
165
  Always filter post queries to exclude deleted or flagged content. Amity's older SDKs include flagged/deleted posts by default, which can leak moderated content into the feed UI if unfiltered.
203
166
  - **Correct pattern:** Apply `.includeDeleted(false)`, `.statuses([.published])`, or `.feedType(AmityFeedType.PUBLISHED)` to your query builders.
204
167
 
168
+ ### Post Review Queue
169
+ When a community has review enabled (`ADMIN_REVIEW_POST_REQUIRED`), posts flow through `reviewing` → `published`/`declined` feeds. A moderation dashboard that only queries the `published` feed silently misses everything awaiting review.
170
+ - **Correct pattern:** For a moderator surface, query `feedType: 'reviewing'` (gated on the `REVIEW_COMMUNITY_POST` permission) and wire `approvePost()` / `declinePost()`. A regular member feed stays on `published`.
171
+ - This is completeness, not deterministically enforced (most feeds aren't moderator dashboards) — match it to whether you're building a moderation/review surface.
172
+
205
173
  ### Opaque Pagination Cursors
206
174
  Amity uses opaque, token-based cursors for pagination. Never attempt to construct the `nextPage` argument using numeric math (e.g., `page * size`). This will cause silent failures or HTTP 400 errors.
207
175
  - **Correct pattern:** Pass the exact `nextPageToken` returned from the previous query result, or call `.loadMore()` / `.nextPage()` on the collection itself.
208
176
  - **Wrong pattern:** `.nextPage(pageNumber * 20)`
209
177
 
210
178
  ### Parent-Child Post Rendering
211
- Amity models image and video attachments as "child posts" attached to a parent post. If your UI only renders the parent post's text, it will silently drop all media attachments.
212
- - **Correct pattern:** Ensure the post-rendering component recursively processes `post.children`, `.childrenPosts`, or calls `getChildren()`.
213
- - **Wrong pattern:** `Text(post.data.text)` with no reference to the post's children.
179
+ Amity attaches rich content — **images, videos, files, polls, and rooms/livestreams** as "child posts" under a parent whose own `dataType` is usually `'text'`. A UI that reads only the parent will silently drop all of it. This applies to **every** media type, not just images: resolving images from `childrenPosts` but gating polls/videos/rooms on the parent's dataType is the most common version of this bug.
180
+ - **Correct pattern:** Resolve every media type from the parent **or** its children, consistently — `post.childrenPosts?.find(c => c.dataType === 'poll')?.getPollInfo()`, same for image/video/room/file. Process `post.children` / `.childrenPosts` / `getChildren()` for all of them.
181
+ - **Wrong pattern:** `Text(post.data.text)` with no children handling or handling children for images only while checking `post.dataType === 'poll'/'video'/'room'` (the parent) for the rest.
182
+
183
+ ### Render Every Post Type the Feed Returns
184
+ A global or community feed query without a `dataTypes` filter returns **all** post types — text, image, video, file, poll, livestream, and room. A renderer that branches on only one or two types silently renders the rest as blank cards. This is the single most common "the feed looks broken" complaint.
185
+ - **Correct pattern:** Resolve each media type from **both the parent and its `childrenPosts`** — in a feed the parent is usually `dataType: 'text'` and the image/video/poll/room rides on a *child* post (see Parent-Child Post Rendering). Use a helper per type, e.g. `post.dataType === 'poll' ? post.getPollInfo() : post.childrenPosts?.find(c => c.dataType === 'poll')?.getPollInfo()` — and the same for image/video/room/file. For polls, read each answer via `answer.data` (a plain string) and `answer.image?.fileUrl`, support `votePoll`/`unvotePoll`, and show `poll.closedAt`/`closedIn`. For room/livestream, display the entity's `title`/`status` — never the raw `roomId`. **And when you render the text body itself (post or comment): if `metadata` carries mention entries (`{ type: 'user', index, length, userId }`, `length` excluding the `@`), wrap each `[index, index + length + 1]` span in a styled element and resolve `userId` to a display name — don't print raw text and lose the @mention highlight.** (Pass `mentionees` on create so mentioned users are notified.)
186
+ - **Wrong pattern:** Gating media on the **parent's** dataType only — `const poll = post.dataType === 'poll' ? post.getPollInfo() : undefined` — while resolving images from children. The feed parent is `'text'`, so that branch never matches and polls/videos/rooms silently never render. Vise's `posts.parent-child-rendered` flags this inconsistency. Likewise, rendering `data.text` directly with no use of `metadata` mention offsets drops @mention highlights.
187
+ - Coverage of *which* types appear is **not** deterministically enforced (a feed may legitimately contain only some types) — it is your responsibility to match rendering to what the feed can return.
188
+
189
+ ### Read SDK Objects Through Their Accessors, Not Casts
190
+ Casting an SDK return value to a hand-written shape — `(post.data as { text?: string }).text` — silences the type-checker. When an SDK upgrade renames a field, the build still compiles and the bug only appears at runtime.
191
+ - **Correct pattern:** Use the SDK's typed accessors and types (`post.getImageInfo()`, `post.getPollInfo()`, the `Amity.*` types) so `tsc`/`analyze` flags field-level breakage on upgrade.
192
+ - **Wrong pattern:** `as { … }` casts over `post.data`, `comment.data`, `answer.data`, or community/user fields.
193
+
194
+ ### Comment Surfaces Need Read, Write, and an Explicit `pageSize`
195
+ A comment thread is incomplete if it only lists comments. Users expect to post.
196
+ - **Correct pattern:** Pair `CommentRepository.getComments({ …, pageSize: 20 }, cb)` (read) with a composer that calls `createComment` (write), gated behind the user's ban state. Always pass an explicit `pageSize` — the default pagination uses skip/limit, which the server rejects for scrollable queries with a runtime 500000 error. Render replies via `getComments({ parentId })` when `childrenNumber > 0`.
197
+ - **Wrong pattern:** A read-only comment list, or `getComments` with no `pageSize`.
214
198
 
215
199
  ### Explicit Feed Target Types
216
200
  When calling `createPost`, do not hardcode the `targetType` to a literal value like `AmityPostTargetType.COMMUNITY` if the composer component is meant to be reusable.
@@ -239,43 +223,13 @@ Amity routes custom-shape post payloads via the `dataType` tag. Omitting it make
239
223
 
240
224
  ## SDK Version Handling
241
225
 
242
- Do not use floating SDK dependency versions such as `latest`, `*`, `any`, `+`, or branch-based iOS package references. Pin the social.plus SDK to an explicit version or reviewed semver range, then let the host app's package manager lockfile capture the exact resolved version.
243
-
244
- ## Attestation Flow
245
-
246
- `vise check` reports each applicable rule as one of `deterministic-pass`, `attestation-needed`, `failed`, or `blocked`. `attestation-needed` and `failed` are NOT warnings to skip — they block "I'm done." Resolve them in one of two ways:
247
-
248
- 1. **Make the deterministic check fire (preferred).** Re-read `vise explain <rule-id>` for the rule's enforcement details, adjust the code so the check passes, then run `vise check` again. After it goes green, run `vise sync` to persist the deterministic-pass attestation files.
249
- 2. **Record an attestation with evidence.** When the rule cannot fire because the customer's architecture is unconventional (DI wrappers, custom abstractions, etc.), use `vise attest`:
226
+ For a **new (greenfield) install**, resolve the **current latest** SDK at install time never copy a version number from examples, docs snippets, or memory, which go stale (e.g. an old major can lack `StoryRepository`). Install latest, then pin the resolved version: `npm install @amityco/ts-sdk@latest` (TypeScript/React Native; the lockfile captures the exact version), or the latest Maven / CocoaPods·SwiftPM / pub.dev release for Android / iOS / Flutter. `vise plan` surfaces an `sdkVersion` field — for TypeScript/React Native it reports the current npm `latest`; **heed it**.
250
227
 
251
- ```bash
252
- vise attest . \
253
- --rule typescript.client.region \
254
- --confidence high \
255
- --signer host-agent \
256
- --rationale "API_REGIONS.EU is explicitly passed as the second argument to Client.createClient in src/socialSDK.ts." \
257
- --evidence-file ./.tmp/evidence.json
258
- ```
259
-
260
- The evidence file is a JSON object matching the `evidence_required` schema for that rule (`vise explain <rule-id>` shows the required fields). Include concrete source file paths in evidence values whenever the evidence depends on code, for example `{ "region_source": "src/socialSDK.ts" }` or `{ "login_flow": "src/auth/socialSession.ts" }`. `vise attest` fingerprints cited files, and future `vise check` runs mark the attestation stale if those files change.
261
-
262
- ### Confidence honesty
263
-
264
- - `high` — direct evidence the deterministic check would have fired if it had been more flexible. Auto-recorded.
265
- - `medium` — reasoned about it but cannot point to a single unambiguous location. May require human signer depending on rule.
266
- - `low` — guess. Always requires `--signer human`.
228
+ Do not use floating SDK dependency versions such as `latest`, `*`, `any`, `+`, or branch-based iOS package references in the committed manifest. Pin the social.plus SDK to an explicit version or reviewed semver range, then let the host app's package manager lockfile capture the exact resolved version. If `vise plan` reports that an **existing** project is on an older major than current latest, treat that as an FYI only — a major upgrade is out of scope unless the task explicitly asks for it.
267
229
 
268
- If the rule's `escalation_threshold` is above your confidence, `vise attest --signer host-agent` is rejected. In v0.4 there is no hosted review queue yet, so the path is: stop, surface to the user with the rule id and the evidence you have, and ask them to run `vise attest --signer human --identity <their name or email>` themselves.
230
+ ## Resolving `vise check` Findings
269
231
 
270
- ### What `vise sync` is NOT
271
-
272
- - `vise sync` does **not** accept `--attest` or any flag. It only persists deterministic-pass results.
273
- - Passing unknown flags to `vise sync` will error in v0.4 — don't try to use it as a shortcut for attestation.
274
- - To record an attestation, use `vise attest`. Always.
275
-
276
- ### Treat warnings as blockers
277
-
278
- If a legacy `vise validate` run reports `needs-review` with warnings, those warnings correspond to compliance rules in `attestation-needed` state. Do not interpret them as "code is fine, just static-analysis noise." Run `vise check` to get the structured rule list, then resolve each one as above.
232
+ `vise check` reports each rule as `deterministic-pass`, `attestation-needed`, `failed`, or `blocked`. **`attestation-needed` and `failed` block "I'm done" — they are not skippable warnings.** Resolve each one of two ways: (1) **preferred** — adjust the code so the deterministic check fires (`vise explain <rule-id>` shows what it wants), then `vise check` green → `vise sync`; or (2) record `vise attest` with evidence when the architecture is genuinely unconventional. Confidence honesty, the `vise attest` evidence-file format, escalation/human-signer rules, and "`vise sync` is not attestation": **see `reference/operations.md`**.
279
233
 
280
234
  ## Feature Guidance
281
235
 
@@ -288,6 +242,14 @@ vise plan . --request "<feed or post request>"
288
242
 
289
243
  Require a concrete target from the app: current user feed, selected community, selected channel, or another user-provided domain object. Do not hardcode random target IDs.
290
244
 
245
+ **Decide engagement scope explicitly — Vise authors the checklist, you subtract with a reason.** `vise plan` returns a `completenessChecklist` (the canonical capabilities for the outcome, e.g. comments, reactions, pagination, polls, media, moderation) and `vise check` reports each as present / missing / opted-out. This is **advisory — it never fails the check**; it exists so completeness doesn't depend on your memory. For each capability: build it, or explicitly opt out with a recorded marker in the code so the omission is reviewable, not accidental:
246
+
247
+ ```
248
+ // vise: scope-omit poll — text + image feed only; polls disabled for this tenant
249
+ ```
250
+
251
+ Do not silently drop a capability. `vise check`'s `completeness` section will keep nudging on anything that's neither built nor opted-out.
252
+
291
253
  **Demo wiring (when the app needs to compile before the real target source exists):** even at the top-level `App` / `MaterialApp` / `_app.tsx` / `AppDelegate` wiring site, do not pass a literal string. Use the host platform's compile-time env channel so the rule sees no string literal at the call site:
292
254
 
293
255
  - Flutter: `const String.fromEnvironment('AMITY_COMMUNITY_ID')` — value injected via `flutter run --dart-define=AMITY_COMMUNITY_ID=…`
@@ -336,6 +298,11 @@ When rendering moderation actions (like `flagPost`, `deletePost`, `banUser`, `mu
336
298
 
337
299
  Similarly, moderator-only data such as `flagCount` must not be leaked to non-moderators. Rendering `post.flagCount` unconditionally exposes moderation signal to all users, which is a privacy regression. Gating it with a role check ensures it is only visible to authorized users.
338
300
 
301
+ **Post/comment actions are gated by the viewer's relationship to the content**, and ownership is distinct from moderator role:
302
+ - **Author** (`post.postedUserId === currentUserId`): show edit (`editPost`/`updateComment`) and delete (`deletePost`/`deleteComment`).
303
+ - **Non-author**: show report/flag (`flagPost`/`flagComment`).
304
+ - A post card with no actions menu at all is incomplete. Do not show edit/delete to non-authors, and do not rely on the server's 403 to hide them.
305
+
339
306
  ### User Ban State
340
307
 
341
308
  Banned users still see interaction buttons (like `createPost`, `createComment`, `sendMessage`) if you don't check their ban state before rendering them. These API calls will fail on the server, causing a poor UX and support tickets.
@@ -367,16 +334,49 @@ Require chat shape (1:1, group, community), channel source (never hardcode chann
367
334
  - Use `community` or `live` for groups.
368
335
  If the type intentionally mismatches the UI name, add `// vise: channel type rationale — <reason>` to the code.
369
336
 
370
- ## Monorepos
337
+ For communities:
371
338
 
372
- If `vise inspect .` reports multiple surfaces, choose the intended app with the user or surrounding task context. Pass it consistently:
339
+ ```bash
340
+ vise search-docs "community create join leave members invitation"
341
+ vise plan . --request "<community request>"
342
+ ```
343
+
344
+ Require the community surface (create, browse/join, detail + members, or membership management), the privacy model (public instant-join vs private join-request approval — they differ), and a concrete community ID source (never hardcode a communityId). Query communities and members through a Live Collection, handle the join-request flow for private communities, gate moderator actions (roles/ban) by role, and clean up observers. `vise plan` returns the community `completenessChecklist`; opt out of anything out of scope with `// vise: scope-omit <id> <reason>`.
345
+
346
+ For the social graph (follow/followers):
347
+
348
+ ```bash
349
+ vise search-docs "follow unfollow followers following block user"
350
+ vise plan . --request "<follow/social-graph request>"
351
+ ```
352
+
353
+ Require the surface (follow button, follower list, following list, or blocked users), the follow model (automatic vs follow-request approval — they differ), and a concrete target userId source (never invent a userId). Query follower/following lists through a Live Collection; handle the follow-request pending/accept/decline flow when following isn't automatic; clean up observers.
354
+
355
+ For an in-app notification tray (distinct from push setup — that's a push/FCM/APNS concern):
373
356
 
374
357
  ```bash
375
- vise plan . --surface apps/web --request "<user intent>"
376
- vise validate . --surface apps/web
377
- vise run-sensors . --surface apps/web
358
+ vise search-docs "in-app notification tray seen settings"
359
+ vise plan . --request "<notification tray request>"
378
360
  ```
379
361
 
362
+ Observe the notification tray as a Live Object/Collection, mark items/tray seen so the unseen badge clears server-side, respect Amity's server-side notification settings, and clean up the observer.
363
+
364
+ ## Design-Conformant UI (Design Contract)
365
+
366
+ The customer wants social.plus UI that looks like *their* app, not a generic kit. Match their aesthetic from whatever design signal exists, in this priority:
367
+
368
+ 1. **Customer-provided prototype (HTML/CSS).** If the customer gives you an HTML/CSS prototype (a static mockup, an existing page, a design export), run `vise design extract <prototypePath> --repo .` first. It writes `sp-vise/design-contract.json` — a *graded* contract: **declared** tokens (CSS custom properties — exact, authoritative) and **inferred** tokens (repeated literal values — advisory). `vise plan` then surfaces this contract in its `designContract` field. Build the feed using the declared tokens; treat inferred tokens as advisory and confirm brand values with the customer when the contract `strength` is `weak`.
369
+ 2. **Host app's own design system.** With no external prototype, run `vise design extract --from-project .` to derive the contract from the project's *own* design system — CSS custom properties (incl. shadcn `:root` and Tailwind v4 `@theme`), TS/JS token modules (`theme.ts`/`tokens.ts`), inline tailwind configs, and native sources (Android `colors.xml`/`dimens.xml`, Flutter `Color(0x…)`, iOS `.xcassets/*.colorset` + Swift colors). Then build the new surface from those tokens (also enforced by `*.design.reuse-detected-tokens`). `vise inspect` lists the detected design signals.
370
+ 3. **Neither.** Use a neutral, accessible default and **state in your summary that no design source was found** — do not invent brand colors, spacing, or a design system. Ask the customer for a prototype, their theme file, or screenshots.
371
+
372
+ To let the customer (or yourself) **see** the contract and check the match, run `vise design preview .` — it writes a self-contained `sp-vise/design-preview.html` with the tokens as visual swatches, the conformance report, and the HTML prototype embedded (`--reference <prototype>`) for side-by-side review. It's advisory: it renders the artifact for a human/VLM to judge, and is not an automated pixel diff (a true render diff belongs to your own browser tooling, not Vise).
373
+
374
+ The design contract is **advisory generation guidance**: it shapes what you build, but it is not a deterministic gate and never fails `vise check`. A token-poor prototype yields a weaker — never wrong — contract. When you record a design attestation, cite the contract `digest` so the feed can be claimed conformant to the prototype.
375
+
376
+ ## Monorepos
377
+
378
+ If `vise inspect .` reports multiple surfaces, pick the intended app and pass `--surface <path>` consistently to `plan`/`validate`/`run-sensors`. Details: `reference/operations.md`.
379
+
380
380
  ## Final Response Discipline
381
381
 
382
382
  Report:
@@ -0,0 +1,39 @@
1
+ # Debugging & Troubleshooting (Day 2 Operations)
2
+
3
+ Load this when the customer reports an SDK-specific runtime crash, build failure, or logical error (e.g. data not loading).
4
+
5
+ Start with `vise debug` before broad repo inspection or sidecar exploration.
6
+
7
+ **CRITICAL SCOPE LIMIT:** Do NOT spam `vise debug` for generic UI/CSS errors or non-SDK issues. ONLY call `vise debug` if the error explicitly contains keywords like `social.plus`, `social-plus`, `Amity`, `live-collection`, or if the error occurs in a file that imports `social.plus` packages.
8
+
9
+ For the first repair pass, prefer the compact troubleshooting loop:
10
+
11
+ ```bash
12
+ vise debug . --error-file "logs/crash.log" --brief
13
+ vise check . --ci
14
+ vise run-sensors .
15
+ ```
16
+
17
+ When debugging, pass the stack trace to Vise:
18
+
19
+ ```bash
20
+ vise debug . --error "stack trace or exception message here"
21
+ ```
22
+ Or, if the error is in a file:
23
+ ```bash
24
+ vise debug . --error-file "logs/crash.log"
25
+ ```
26
+ Or, for a compact repair brief:
27
+ ```bash
28
+ vise debug . --error-file "logs/crash.log" --brief
29
+ ```
30
+
31
+ The output contains `likelyCause`, `correlatedRules`, and a compact `repairBrief` with:
32
+
33
+ - the primary rule to repair first
34
+ - likely file hints
35
+ - minimum patch shape
36
+ - invariants to preserve
37
+ - verification commands
38
+
39
+ Do not guess the fix before consulting `vise debug`. Do not read broad `sp-vise/` history on the first pass unless the `repairBrief` is ambiguous or the first repair fails.
@@ -0,0 +1,59 @@
1
+ # Operations: Attestation, Engagement Scope, Monorepos
2
+
3
+ Load this when resolving `vise check` findings, working with engagement scope, or operating in a monorepo.
4
+
5
+ ## Attestation Flow
6
+
7
+ `vise check` reports each applicable rule as one of `deterministic-pass`, `attestation-needed`, `failed`, or `blocked`. `attestation-needed` and `failed` are NOT warnings to skip — they block "I'm done." Resolve them in one of two ways:
8
+
9
+ 1. **Make the deterministic check fire (preferred).** Re-read `vise explain <rule-id>` for the rule's enforcement details, adjust the code so the check passes, then run `vise check` again. After it goes green, run `vise sync` to persist the deterministic-pass attestation files.
10
+ 2. **Record an attestation with evidence.** When the rule cannot fire because the customer's architecture is unconventional (DI wrappers, custom abstractions, etc.), use `vise attest`:
11
+
12
+ ```bash
13
+ vise attest . \
14
+ --rule typescript.client.region \
15
+ --confidence high \
16
+ --signer host-agent \
17
+ --rationale "API_REGIONS.EU is explicitly passed as the second argument to Client.createClient in src/socialSDK.ts." \
18
+ --evidence-file ./.tmp/evidence.json
19
+ ```
20
+
21
+ The evidence file is a JSON object matching the `evidence_required` schema for that rule (`vise explain <rule-id>` shows the required fields). Include concrete source file paths in evidence values whenever the evidence depends on code, for example `{ "region_source": "src/socialSDK.ts" }` or `{ "login_flow": "src/auth/socialSession.ts" }`. `vise attest` fingerprints cited files, and future `vise check` runs mark the attestation stale if those files change.
22
+
23
+ ### Confidence honesty
24
+
25
+ - `high` — direct evidence the deterministic check would have fired if it had been more flexible. Auto-recorded.
26
+ - `medium` — reasoned about it but cannot point to a single unambiguous location. May require human signer depending on rule.
27
+ - `low` — guess. Always requires `--signer human`.
28
+
29
+ If the rule's `escalation_threshold` is above your confidence, `vise attest --signer host-agent` is rejected. In v0.4 there is no hosted review queue yet, so the path is: stop, surface to the user with the rule id and the evidence you have, and ask them to run `vise attest --signer human --identity <their name or email>` themselves.
30
+
31
+ ### What `vise sync` is NOT
32
+
33
+ - `vise sync` does **not** accept `--attest` or any flag. It only persists deterministic-pass results.
34
+ - Passing unknown flags to `vise sync` will error in v0.4 — don't try to use it as a shortcut for attestation.
35
+ - To record an attestation, use `vise attest`. Always.
36
+
37
+ ### Treat warnings as blockers
38
+
39
+ If a legacy `vise validate` run reports `needs-review` with warnings, those warnings correspond to compliance rules in `attestation-needed` state. Do not interpret them as "code is fine, just static-analysis noise." Run `vise check` to get the structured rule list, then resolve each one as above.
40
+
41
+ ## Engagement Scope (Optional)
42
+
43
+ If the customer has set up `sp-vise/engagement.json` (via `vise engagement init`), respect the declared scope. `vise engagement show .` prints the engagement metadata: tier, customer id, contracted outcomes, reviewer assignment.
44
+
45
+ When `vise init` reports a `warnings` array containing "not in the engagement scope", surface that to the user — don't silently widen scope. The user either extends the scope by editing `engagement.json` or accepts that the current work is out-of-contract.
46
+
47
+ If no `engagement.json` exists, do not create one without the user's intent. `vise engagement init` is a contractual artifact; agents should only run it when the user explicitly asks to set up engagement metadata, typically with `--tier`, `--customer-id`, and `--scope`.
48
+
49
+ If `vise` is not installed, use `npm install -g @amityco/social-plus-vise`, then retry the `vise` command.
50
+
51
+ ## Monorepos
52
+
53
+ If `vise inspect .` reports multiple surfaces, choose the intended app with the user or surrounding task context. Pass it consistently:
54
+
55
+ ```bash
56
+ vise plan . --surface apps/web --request "<user intent>"
57
+ vise validate . --surface apps/web
58
+ vise run-sensors . --surface apps/web
59
+ ```