@amityco/social-plus-vise 0.4.0 → 0.7.0

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.
@@ -157,6 +157,66 @@
157
157
  "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "react-native.logging.no-secret-in-log" }],
158
158
  "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "redaction_policy", "description": "Where the value being logged is redacted or proven to be a placeholder.", "upload_policy": "upload-with-consent" }] }
159
159
  }
160
+ },
161
+ {
162
+ "id": "android.logging.no-pii-in-log",
163
+ "version": 1,
164
+ "title": "Android logging must not include PII-shaped values",
165
+ "severity": "warning",
166
+ "rationale": "Logging userName, email, phone, displayName, address, or other PII-shaped values to Log.* or println exposes user data to crash reports, log aggregators, and device logs. Redact or drop the value.",
167
+ "applies_when": { "platforms": ["android"], "outcomes": ["setup-sdk", "setup-push", "setup-live-data", "add-feed", "validate-setup"] },
168
+ "enforcement": {
169
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "android.logging.no-pii-in-log" }],
170
+ "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "redaction_policy", "description": "Explanation of how PII is redacted or why the logged value is not real user PII.", "upload_policy": "upload-with-consent" }] }
171
+ }
172
+ },
173
+ {
174
+ "id": "flutter.logging.no-pii-in-log",
175
+ "version": 1,
176
+ "title": "Flutter logging must not include PII-shaped values",
177
+ "severity": "warning",
178
+ "rationale": "Logging userName, email, phone, displayName, address, or other PII-shaped values to print, debugPrint, or developer.log exposes user data to crash reports and log aggregators. Redact or drop the value.",
179
+ "applies_when": { "platforms": ["flutter"], "outcomes": ["setup-sdk", "setup-push", "setup-live-data", "add-feed", "validate-setup"] },
180
+ "enforcement": {
181
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "flutter.logging.no-pii-in-log" }],
182
+ "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "redaction_policy", "description": "Explanation of how PII is redacted or why the logged value is not real user PII.", "upload_policy": "upload-with-consent" }] }
183
+ }
184
+ },
185
+ {
186
+ "id": "ios.logging.no-pii-in-log",
187
+ "version": 1,
188
+ "title": "iOS logging must not include PII-shaped values",
189
+ "severity": "warning",
190
+ "rationale": "Logging userName, email, phone, displayName, address, or other PII-shaped values via print, NSLog, os_log, or Logger exposes user data to system logs and crash reports. Redact or drop the value.",
191
+ "applies_when": { "platforms": ["ios"], "outcomes": ["setup-sdk", "setup-push", "setup-live-data", "add-feed", "validate-setup"] },
192
+ "enforcement": {
193
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "ios.logging.no-pii-in-log" }],
194
+ "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "redaction_policy", "description": "Explanation of how PII is redacted or why the logged value is not real user PII.", "upload_policy": "upload-with-consent" }] }
195
+ }
196
+ },
197
+ {
198
+ "id": "typescript.logging.no-pii-in-log",
199
+ "version": 1,
200
+ "title": "TypeScript logging must not include PII-shaped values",
201
+ "severity": "warning",
202
+ "rationale": "Logging userName, email, phone, displayName, address, or other PII-shaped values via console.* exposes user data to browser devtools, server logs, and error trackers. Redact or drop the value.",
203
+ "applies_when": { "platforms": ["typescript"], "outcomes": ["setup-sdk", "setup-push", "setup-live-data", "add-feed", "validate-setup"] },
204
+ "enforcement": {
205
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "typescript.logging.no-pii-in-log" }],
206
+ "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "redaction_policy", "description": "Explanation of how PII is redacted or why the logged value is not real user PII.", "upload_policy": "upload-with-consent" }] }
207
+ }
208
+ },
209
+ {
210
+ "id": "react-native.logging.no-pii-in-log",
211
+ "version": 1,
212
+ "title": "React Native logging must not include PII-shaped values",
213
+ "severity": "warning",
214
+ "rationale": "Logging userName, email, phone, displayName, address, or other PII-shaped values via console.* exposes user data to Metro, Flipper, and crash reports. Redact or drop the value.",
215
+ "applies_when": { "platforms": ["react-native"], "outcomes": ["setup-sdk", "setup-push", "setup-live-data", "add-feed", "validate-setup"] },
216
+ "enforcement": {
217
+ "deterministic": [{ "check": "validator-finding-absent", "finding_rule_id": "react-native.logging.no-pii-in-log" }],
218
+ "attestation": { "allowed": true, "host_agent_min_confidence": "high", "human_allowed": true, "evidence_required": [{ "field": "redaction_policy", "description": "Explanation of how PII is redacted or why the logged value is not real user PII.", "upload_policy": "upload-with-consent" }] }
219
+ }
160
220
  }
161
221
  ]
162
222
  }
@@ -61,6 +61,9 @@ Ask the user before editing when required values are missing:
61
61
 
62
62
  - region, auth/user identity, or session-handler ownership (which class/module implements `sessionWillRenewAccessToken`)
63
63
  - feed target, community ID, target ID, feed ID, or channel ID
64
+ - comment target source (which parent entity provides postId/referenceId)
65
+ - moderation target types (post, comment, message, user), action set, or rendering policy for blocked content
66
+ - chat shape (1:1, group, community), channel source (never invent channelId), or message capabilities scope
64
67
  - notification platform type, token source, permission owner, or logout cleanup owner
65
68
  - Live Object/Collection domain, object vs collection choice, lifecycle owner, or cleanup location
66
69
  - app surface in a monorepo when Vise reports multiple surfaces
@@ -106,7 +109,90 @@ Per platform:
106
109
  - Flutter Dart: implement `AmitySessionHandler.sessionWillRenewAccessToken(AmityAccessTokenRenewal)` and call `renewal.renew()`.
107
110
  - iOS Swift: implement `AmitySessionHandler.sessionWillRenewAccessToken(renewal:)` and call `renewal.renew()`.
108
111
 
109
- `vise check` enforces `*.session.renewal` per platform when login is present.
112
+ ### Session Handler Retention
113
+
114
+ **CRITICAL:** The session handler object must outlive the function that created it. If you declare the session handler as a function-local variable (e.g. inside `setUp()`, `initState()`, `onCreate()`), the host language's garbage collector (Dart, JS, Kotlin) or ARC (iOS) will destroy it before the SDK ever needs to renew the session.
115
+
116
+ - **Correct pattern:** Declare it as a class-level property/field, a module-scoped constant, or retain it via a state container (e.g. `useRef` in React).
117
+ - **Wrong pattern:** `func setUp() { let handler = AppSessionHandler(); ... login(..., handler) }` — handler dies at the end of `setUp()`.
118
+
119
+ `vise check` enforces `*.session.renewal` per platform when login is present, and `*.session-handler.retained` to prevent garbage-collection bugs.
120
+
121
+ ## Notifications & Media
122
+
123
+ ### Notification Preferences
124
+
125
+ Amity has its own server-side notification preferences (e.g. mute community, mute user, granular per-event toggles) that are entirely separate from FCM/APNS. Customers often register the push token and stop there. As a result, notifications fire but ignore the user's preferences, leading to bug reports.
126
+
127
+ - **Correct pattern**: Register the push token, then load or observe Amity's notification settings (e.g. `AmityCoreClient.notifications().getSettings()`, `notificationRepository.getSettings()`).
128
+ - **Wrong pattern**: Only calling `registerPushNotification` without ever inspecting or respecting Amity's own notification settings.
129
+
130
+ ### Unread Counts
131
+
132
+ Amity exposes a global `unreadCount` stream that syncs across devices.
133
+
134
+ - **Correct pattern**: Subscribe to the unread stream provided by the SDK (e.g. `AmityCoreClient.unreadCount()`, `getUnreadCount()`, `unread.stream`).
135
+ - **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.
136
+
137
+ ### File Upload & Media
138
+
139
+ 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.
140
+
141
+ - **Correct pattern**: Upload media through `AmityFileClient` or `AmityFileRepository` to get an `AmityFileId`, then attach the file ID to the post.
142
+ - **Wrong pattern**: Passing external URLs directly in the `attachments` array or object when creating a post.
143
+
144
+ ### Image & Video Posts
145
+
146
+ After creating a post with an image or video attachment, the parent post returns immediately, but the image child post is still being processed server-side.
147
+
148
+ - **Correct pattern**: Await the child post's ready state (e.g. `await result.children.first.whenReady`) or observe the post before rendering.
149
+ - **Wrong pattern**: Rendering the result immediately after creation, yielding an empty image or broken thumbnail.
150
+
151
+ ## Data Correctness
152
+
153
+ ### Live Collection API Mismatch
154
+ When querying lists of entities (e.g., `getPosts`, `getCommunityFeed`), always use reactive LiveCollection APIs instead of Future-based one-shot queries. One-shot APIs do not listen for real-time updates or local mutations, leading to stale UIs.
155
+ - **Correct pattern:** Observe the collection (e.g., `.observe()`, `.listen()`, `StreamBuilder`, `LiveData`, `getLiveCollection()`) and render the stream.
156
+ - **Wrong pattern:** `await getPosts(...)` and assigning to a static array.
157
+
158
+ ### Post Status Filters
159
+ 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.
160
+ - **Correct pattern:** Apply `.includeDeleted(false)`, `.statuses([.published])`, or `.feedType(AmityFeedType.PUBLISHED)` to your query builders.
161
+
162
+ ### Opaque Pagination Cursors
163
+ 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.
164
+ - **Correct pattern:** Pass the exact `nextPageToken` returned from the previous query result, or call `.loadMore()` / `.nextPage()` on the collection itself.
165
+ - **Wrong pattern:** `.nextPage(pageNumber * 20)`
166
+
167
+ ### Parent-Child Post Rendering
168
+ 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.
169
+ - **Correct pattern:** Ensure the post-rendering component recursively processes `post.children`, `.childrenPosts`, or calls `getChildren()`.
170
+ - **Wrong pattern:** `Text(post.data.text)` with no reference to the post's children.
171
+
172
+ ### Explicit Feed Target Types
173
+ When calling `createPost`, do not hardcode the `targetType` to a literal value like `AmityPostTargetType.COMMUNITY` if the composer component is meant to be reusable.
174
+ - **Correct pattern:** Pass the `targetType` dynamically (e.g. from props, parameters, or intent extras) so the composer can post to a user feed, community feed, or global feed interchangeably.
175
+ - **Wrong pattern:** Hardcoding `targetType: AmityPostTargetType.COMMUNITY` inside a generic post composer.
176
+
177
+ ### Comment Reference Type Enum
178
+ When creating comments, always use the SDK's provided enum types for the reference type rather than raw string literals.
179
+ - **Correct pattern:** `AmityCommentReferenceType.POST`, `AmityCommentReferenceType.post`, etc.
180
+ - **Wrong pattern:** Passing `"POST"` or `'post'` directly as a string literal.
181
+
182
+ ### Channel Type Matches Semantic Shape
183
+ `AmityChannelType` exposes four channel shapes — `community`, `conversation`, `broadcast`, `live` — each with different capabilities (membership, messaging, presence). Picking the wrong type silently breaks conversation semantics: 1:1 chats end up in the community list, broadcasts allow replies, etc.
184
+ - **Correct pattern:** Use `.conversation` for 1:1 or small private chats, `.community` for many-to-many public discussion, `.broadcast` for announcement-style one-to-many, `.live` for live-stream chat. If the component is reusable across types, accept `channelType` as a parameter / prop.
185
+ - **Wrong pattern:** A `DirectMessageScreen` or `OneOnOneChat` component that calls `createChannel(channelType: AmityChannelType.COMMUNITY, ...)`.
186
+
187
+ ### Reaction Names Match Console Config
188
+ Reaction names are configured per-tenant in the social.plus console. Hardcoding `"like"` because the docs show it will silently no-op or 400 if the tenant configured `"heart"` or `"clap"` instead.
189
+ - **Correct pattern:** Import reaction names from a shared config module (e.g. `import { LIKE_REACTION } from './reactions';`), receive them as a parameter, or fetch them from runtime tenant config.
190
+ - **Wrong pattern:** `post.addReaction("like")` — the literal string couples your client to one specific console state.
191
+
192
+ ### Custom Post Types Must Declare `dataType`
193
+ Amity routes custom-shape post payloads via the `dataType` tag. Omitting it makes the SDK fall back to text rendering — your custom data renders as a JSON-stringified blob in the UI.
194
+ - **Correct pattern:** `createPost({ dataType: "custom.poll", data: { question, options } })` — both fields together.
195
+ - **Wrong pattern:** `createPost({ data: { score: 100, level: "expert" } })` with no `dataType` set.
110
196
 
111
197
  ## SDK Version Handling
112
198
 
@@ -159,6 +245,18 @@ vise plan . --request "<feed or post request>"
159
245
 
160
246
  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.
161
247
 
248
+ **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:
249
+
250
+ - Flutter: `const String.fromEnvironment('AMITY_COMMUNITY_ID')` — value injected via `flutter run --dart-define=AMITY_COMMUNITY_ID=…`
251
+ - TypeScript / React / Next.js: `process.env.NEXT_PUBLIC_AMITY_COMMUNITY_ID` (or framework equivalent — `EXPO_PUBLIC_`, `VITE_`)
252
+ - React Native: `process.env.AMITY_COMMUNITY_ID` (via `react-native-config` or a build-time replacement)
253
+ - Android: `BuildConfig.AMITY_COMMUNITY_ID` populated from `buildConfigField` in `build.gradle`
254
+ - iOS: `ProcessInfo.processInfo.environment["AMITY_COMMUNITY_ID"]` or an `xcconfig` build setting
255
+
256
+ If the customer prefers a runtime source (route param, app state, user selection), document the source in the attestation rather than committing a placeholder literal.
257
+
258
+ **Custom Post Types:** When creating a post with a custom data payload (e.g. an object or dictionary), the `dataType` tag must be explicitly provided in the same call so the SDK routes it correctly. If the custom payload intentionally falls back to a text post, add `// vise: custom post type <reason>` to the code.
259
+
162
260
  For notification setup:
163
261
 
164
262
  ```bash
@@ -177,6 +275,55 @@ vise plan . --request "<live data request>"
177
275
 
178
276
  Require object vs collection, observed domain, lifecycle owner, loading/error/update states, and observer/subscription cleanup.
179
277
 
278
+ For comments:
279
+
280
+ ```bash
281
+ vise search-docs "comment reply thread create observe"
282
+ vise plan . --request "<comment request>"
283
+ ```
284
+
285
+ Require target source (post ID from parent entity, not hardcoded), observer cleanup on view disposal, loading/empty/error states, and moderation affordance on comment content.
286
+
287
+ ### Moderation UX
288
+
289
+ When rendering moderation actions (like `flagPost`, `deletePost`, `banUser`, `muteChannelMember`), you must ensure these actions are gated by a role check. Showing these buttons to all users causes a poor UX since the API calls will fail for non-moderators.
290
+
291
+ - **Correct pattern**: Wrap the button or action in a role check (e.g. `currentUser.roles.contains('moderator')`, `isModerator`, `permissions.includes('moderation')`).
292
+ - **Wrong pattern**: Unconditionally rendering `flagPost` or `banUser` and relying on the server to return a 403 error.
293
+
294
+ 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.
295
+
296
+ ### User Ban State
297
+
298
+ 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.
299
+
300
+ - **Correct pattern**: Wrap interaction buttons in a ban state check (e.g. `!currentUser.isGlobalBan`, `!user.banState`, `!channel.isMuted`).
301
+ - **Wrong pattern**: Unconditionally rendering `createPost` or `sendMessage` buttons for a banned user.
302
+
303
+ For reactions:
304
+
305
+ ```bash
306
+ vise search-docs "reaction add remove list"
307
+ vise plan . --request "<reaction request>"
308
+ ```
309
+
310
+ Reaction names are per-tenant. Retrieve the reaction name from a configuration file, backend response, or component prop instead of hardcoding a string literal like `"like"`. If the literal matches the console config, add `// vise: reaction "<name>" matches console config` to the code.
311
+
312
+ For chat/messaging:
313
+
314
+ ```bash
315
+ vise search-docs "chat channel message send observe"
316
+ vise plan . --request "<chat request>"
317
+ ```
318
+
319
+ Require chat shape (1:1, group, community), channel source (never hardcode channelId), message observer cleanup on view dismissal, send error handling, and moderation affordance on messages.
320
+
321
+ **Channel Types:** When creating channels, the channel type must match the semantic shape of the UI:
322
+ - Use `conversation` for 1:1, DM, or private-chat semantics.
323
+ - Use `broadcast` for one-way announcement channels.
324
+ - Use `community` or `live` for groups.
325
+ If the type intentionally mismatches the UI name, add `// vise: channel type rationale — <reason>` to the code.
326
+
180
327
  ## Monorepos
181
328
 
182
329
  If `vise inspect .` reports multiple surfaces, choose the intended app with the user or surrounding task context. Pass it consistently: