@amityco/social-plus-vise 0.14.19 → 0.14.20

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,17 @@ All notable changes to `@amityco/social-plus-vise` are documented in this file.
4
4
 
5
5
  The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 0.14.20 — 2026-06-05
8
+
9
+ ### Changed
10
+ - **Product-first community/follow/notification flow:** community, follow/social-graph, and in-app notification plans now surface shared product expectation IDs before implementation, backed by platform capability preflight.
11
+ - **Community deterministic bindings:** community avatar, community display-name, and role-gated moderator action findings now report shared IDs (`community.avatar-from-sdk`, `community.display-name-from-sdk`, `moderation.role-gated-action`) while retaining exact platform `contractRuleId`/`validator.sensorId` evidence.
12
+ - **Attestation fallback for non-deterministic product expectations:** community target/live/privacy, follow target/live/model, and notification tray/seen/preferences expectations are offered when bundled SDK facts show the surface exists, with `attestation-needed` status until dedicated deterministic sensors exist.
13
+ - **Availability-aware notification guidance:** `vise plan` now withholds unavailable notification tray/mark-seen capabilities from Flutter while still surfacing notification preferences, and withholds notification preferences from TypeScript/React Native where bundled facts do not expose notification settings APIs.
14
+
15
+ ### Verified
16
+ - Full `npm run validate` passed, including package E2E and pack dry-run for `@amityco/social-plus-vise@0.14.20`. Product-flow now verifies community/follow/notification plan feed-forward, unavailable notification capability reporting, and exact Android sensor evidence for promoted community/moderation expectations.
17
+
7
18
  ## 0.14.19 — 2026-06-05
8
19
 
9
20
  ### Changed
package/README.md CHANGED
@@ -161,14 +161,14 @@ Aggregate: **98/99 expected feed capabilities** and **27/27 selected optional ca
161
161
 
162
162
  ### Current Release Validation
163
163
 
164
- Version 0.14.19 carries current release proof around the full feed-forward, product-expectation, and validation flow:
164
+ Version 0.14.20 carries current release proof around the full feed-forward, product-expectation, and validation flow:
165
165
 
166
166
  | Surface | What was validated |
167
167
  |---|---|
168
168
  | **Product flow** | Local end-to-end smoke covers design extraction, plan feed-forward, blocking intake, answered init, capability check, design conformance, and sensor discovery. |
169
169
  | **Plan questions** | Plans surface blocking questions such as `feature_surface` and `design_contract_confirmation`, plus optional choices such as `feed_optional_capabilities`. |
170
170
  | **Capability-to-sensor flow** | Vise checks platform support, matches the prompt to available capabilities, offers supported features as questions, records answers, and turns selected answers into sensors in `vise check`. |
171
- | **Shared product expectations** | Public IDs such as `feed.target-resolved`, `comments.target-resolved`, `comments.thread-read-write`, `chat.channel-target-resolved`, `chat.send-error-handling`, and `chat.unread-visible` stay platform-agnostic while check results retain concrete `contractRuleId` and `validator.sensorId` evidence. |
171
+ | **Shared product expectations** | Public IDs such as `feed.target-resolved`, `comments.thread-read-write`, `chat.channel-target-resolved`, `community.avatar-from-sdk`, `community.display-name-from-sdk`, `moderation.role-gated-action`, `follow.relationship-live`, `profile.social-counts`, and `notifications.tray-live` stay platform-agnostic while check results retain concrete `contractRuleId` and `validator.sensorId` evidence when deterministic sensors exist. |
172
172
  | **Rule detection** | TP-track dashboard detects **321/321 seeded rule gaps (100.0%)** in the static corpus. |
173
173
  | **Packed-package smoke** | Packed-package and host-agent smokes exercise the release tarball path, surfaced plan questions, selected optional capability sensors, rejected design confirmation handling, and exact contract-rule evidence for shared product expectations. |
174
174
 
@@ -688,6 +688,132 @@ export const SHARED_PRODUCT_EXPECTATIONS = [
688
688
  deterministicPlatforms: ["android", "flutter", "ios", "typescript"],
689
689
  hint: "declare first-created/newest-created order in the SDK query or a clearly named UI sort so the thread cannot be reversed by defaults",
690
690
  },
691
+ {
692
+ id: "community.target-resolved",
693
+ label: "Resolved community target",
694
+ outcomes: ["add-community"],
695
+ kind: "shared-expectation",
696
+ availability: [
697
+ {
698
+ label: "SDK community query/target APIs",
699
+ symbols: [/\bcommunityId\b/i, /\bCommunityRepository\b/i, /\bgetCommunity\b/i, /\bqueryCommunities\b/i, /\bgetCommunities\b/i, /\bAmityCommunity\b/i],
700
+ },
701
+ ],
702
+ deterministicPlatforms: [],
703
+ hint: "resolve communityId from route params, user selection, SDK query, or a create flow; do not invent or hardcode it",
704
+ },
705
+ {
706
+ id: "community.members-live",
707
+ label: "Live community/member lists",
708
+ outcomes: ["add-community"],
709
+ kind: "shared-expectation",
710
+ availability: [
711
+ {
712
+ label: "SDK community/member live APIs",
713
+ symbols: [/\bCommunityLiveCollection\b/i, /\bCommunityMemberLiveCollection\b/i, /\bgetMembers\b/i, /\bsearchMembers\b/i, /\bgetLiveCollection\b/i, /\blisten\b/i, /\bobserve\b/i],
714
+ },
715
+ ],
716
+ deterministicPlatforms: [],
717
+ hint: "query communities and members through the SDK live collection/listener idiom, with lifecycle cleanup",
718
+ },
719
+ {
720
+ id: "community.privacy-flow-explicit",
721
+ label: "Community privacy/join flow",
722
+ outcomes: ["add-community"],
723
+ kind: "shared-expectation",
724
+ availability: [
725
+ {
726
+ label: "SDK join/leave and join-request APIs",
727
+ symbols: [/\bjoinCommunity\b/i, /\bleaveCommunity\b/i, /\bjoinRequest\b/i, /\bJoinRequest\b/i, /\bCommunityType\b/i, /\bMembershipAcceptanceType\b/i],
728
+ },
729
+ ],
730
+ deterministicPlatforms: [],
731
+ hint: "handle public instant-join and private join-request states explicitly instead of assuming one community privacy model",
732
+ },
733
+ {
734
+ id: "community.avatar-from-sdk",
735
+ label: "Community avatar from SDK",
736
+ outcomes: ["add-feed", "add-comments", "add-community"],
737
+ kind: "shared-expectation",
738
+ availability: [
739
+ {
740
+ label: "SDK avatar fields/accessors",
741
+ symbols: [/\bAmityCommunity\b/i, /\bAmity\.Community\b/i, /\bavatar\b/i, /\bavatarImage\b/i, /\bgetAvatar\b/i, /\bfileUrl\b/i, /\bfileURL\b/i],
742
+ },
743
+ ],
744
+ deterministicPlatforms: ["android", "flutter", "ios", "typescript"],
745
+ hint: "render the SDK avatar URL when present and fall back to initials only when no SDK avatar exists",
746
+ },
747
+ {
748
+ id: "community.display-name-from-sdk",
749
+ label: "Community display name from SDK",
750
+ outcomes: ["add-feed", "add-comments", "add-community"],
751
+ kind: "shared-expectation",
752
+ availability: [
753
+ {
754
+ label: "SDK community display name",
755
+ symbols: [/\bdisplayName\b/i, /\bgetDisplayName\b/i],
756
+ },
757
+ ],
758
+ deterministicPlatforms: ["android", "flutter", "ios", "typescript"],
759
+ hint: "render community.displayName or the platform equivalent; do not show raw community IDs as names",
760
+ },
761
+ {
762
+ id: "moderation.role-gated-action",
763
+ label: "Role-gated moderator actions",
764
+ outcomes: ["add-community", "add-moderation"],
765
+ kind: "shared-expectation",
766
+ availability: [
767
+ {
768
+ label: "SDK role or member-moderation APIs",
769
+ symbols: [/\baddRole\b/i, /\bremoveRole\b/i, /\bbanMember\b/i, /\bremoveMember\b/i, /\broles?\b/i, /\bpermissions\b/i, /\bmoderation\b/i],
770
+ },
771
+ ],
772
+ deterministicPlatforms: ["android", "flutter", "ios", "typescript"],
773
+ hint: "gate moderator-only role, ban, remove, mute, approve, or decline actions behind SDK role/permission state",
774
+ },
775
+ {
776
+ id: "follow.target-resolved",
777
+ label: "Resolved follow target",
778
+ outcomes: ["add-follow"],
779
+ kind: "shared-expectation",
780
+ availability: [
781
+ {
782
+ label: "SDK user relationship target APIs",
783
+ symbols: [/\buserId\b/i, /\btargetUserId\b/i, /\bUserRepository\b/i, /\bfollow\b/i, /\bunfollow\b/i, /\bgetFollowInfo\b/i],
784
+ },
785
+ ],
786
+ deterministicPlatforms: [],
787
+ hint: "resolve the target userId from route params, selected profile, or current user context; do not invent it",
788
+ },
789
+ {
790
+ id: "follow.relationship-live",
791
+ label: "Live follow relationship",
792
+ outcomes: ["add-follow"],
793
+ kind: "shared-expectation",
794
+ availability: [
795
+ {
796
+ label: "SDK follower/following live APIs",
797
+ symbols: [/\bFollowerLiveCollection\b/i, /\bFollowingLiveCollection\b/i, /\bgetFollowers\b/i, /\bgetFollowings\b/i, /\bgetLiveCollection\b/i, /\blisten\b/i, /\bonFollow/i],
798
+ },
799
+ ],
800
+ deterministicPlatforms: [],
801
+ hint: "keep follow state and follower/following lists live through SDK subscriptions or live collections, with cleanup",
802
+ },
803
+ {
804
+ id: "follow.model-explicit",
805
+ label: "Follow approval model",
806
+ outcomes: ["add-follow"],
807
+ kind: "shared-expectation",
808
+ availability: [
809
+ {
810
+ label: "SDK follow status/request APIs",
811
+ symbols: [/\bFollowStatus\b/i, /\bFollowStatusFilter\b/i, /\bpending\b/i, /\bacceptMyFollower\b/i, /\bdeclineMyFollower\b/i],
812
+ },
813
+ ],
814
+ deterministicPlatforms: [],
815
+ hint: "make automatic follow vs pending approval explicit, including accept/decline states where the SDK exposes them",
816
+ },
691
817
  {
692
818
  id: "profile.social-counts",
693
819
  label: "Profile follower/following counts",
@@ -709,6 +835,54 @@ export const SHARED_PRODUCT_EXPECTATIONS = [
709
835
  deterministicPlatforms: ["android", "flutter", "ios", "typescript"],
710
836
  hint: "if follower/following labels are rendered, source the counts or lists from the SDK instead of placeholders",
711
837
  },
838
+ {
839
+ id: "notifications.tray-live",
840
+ label: "Live notification tray",
841
+ outcomes: ["add-notifications"],
842
+ kind: "shared-expectation",
843
+ availability: [
844
+ {
845
+ label: "SDK notification tray APIs",
846
+ symbols: [/\bnotificationTray\b/i, /\bNotificationTray\b/i, /\bgetNotificationTrayItems\b/i, /\bgetNotificationTraySeen\b/i],
847
+ },
848
+ ],
849
+ deterministicPlatforms: [],
850
+ hint: "observe the in-app notification tray/list or seen object through the SDK; do not treat it as push setup",
851
+ },
852
+ {
853
+ id: "notifications.mark-seen",
854
+ label: "Notification seen state",
855
+ outcomes: ["add-notifications"],
856
+ kind: "shared-expectation",
857
+ availability: [
858
+ {
859
+ label: "SDK tray seen/mark-seen APIs",
860
+ symbols: [/\bmarkTraySeen\b/i, /\bmarkItemsSeen\b/i, /\bmarkSeen\b/i, /\bNotificationTraySeen\b/i],
861
+ },
862
+ ],
863
+ deterministicPlatforms: [],
864
+ hint: "call the SDK mark-seen API so tray badges clear server-side after the user opens the notification surface",
865
+ },
866
+ {
867
+ id: "notifications.preferences-respected",
868
+ label: "Notification preferences",
869
+ outcomes: ["add-notifications"],
870
+ kind: "shared-expectation",
871
+ availability: [
872
+ {
873
+ label: "SDK notification settings/preferences APIs",
874
+ symbols: [
875
+ /\bAmity(?:Channel|Community|User)Notifications?Manager\.getSettings\b/i,
876
+ /\bAmity(?:Channel|Community|User)Notification\.getSettings\b/i,
877
+ /\bAmitySocialClient\.getSettings\b/i,
878
+ /\bnotificationRepository\.getSettings\b/i,
879
+ /\bnotifications\(\)\.getSettings\b/i,
880
+ ],
881
+ },
882
+ ],
883
+ deterministicPlatforms: [],
884
+ hint: "when settings are in scope, read or respect SDK notification preferences rather than showing hardcoded toggles",
885
+ },
712
886
  ];
713
887
  function availabilityDefinitionsFor(outcome) {
714
888
  return [
package/dist/outcomes.js CHANGED
@@ -903,6 +903,7 @@ const addModeration = {
903
903
  `${platform}.moderation.block-or-mute-state-applied`,
904
904
  `${platform}.moderation.hidden-content-rendering-present`,
905
905
  `${platform}.moderation.confirmation-ux-present`,
906
+ "moderation.role-gated-action",
906
907
  ],
907
908
  stopConditions: (ctx) => filterStops(ctx.answers, [
908
909
  { id: "moderation_target", text: "The moderation target content types are unknown." },
@@ -1156,10 +1157,12 @@ const addCommunity = {
1156
1157
  ];
1157
1158
  },
1158
1159
  validation: () => [
1159
- "community membership uses a Live Collection (not a one-shot query)",
1160
- "join/leave handles public vs private (join-request) flows",
1161
- "no invented communityId",
1162
- "community/member observer cleaned up on lifecycle end",
1160
+ "community.target-resolved",
1161
+ "community.members-live",
1162
+ "community.privacy-flow-explicit",
1163
+ "community.avatar-from-sdk",
1164
+ "community.display-name-from-sdk",
1165
+ "moderation.role-gated-action",
1163
1166
  "validate_setup",
1164
1167
  "run_sensors",
1165
1168
  ],
@@ -1275,11 +1278,10 @@ const addFollow = {
1275
1278
  ];
1276
1279
  },
1277
1280
  validation: () => [
1278
- "follower/following lists use a Live Collection (not a one-shot query)",
1279
- "follow model handled (automatic vs follow-request approval)",
1281
+ "follow.target-resolved",
1282
+ "follow.relationship-live",
1283
+ "follow.model-explicit",
1280
1284
  "profile.social-counts",
1281
- "no invented userId",
1282
- "relationship observer cleaned up on lifecycle end",
1283
1285
  "validate_setup",
1284
1286
  "run_sensors",
1285
1287
  ],
@@ -1373,9 +1375,9 @@ const addNotifications = {
1373
1375
  ];
1374
1376
  },
1375
1377
  validation: () => [
1376
- "notification tray observed as a Live Object/Collection (not a one-shot query)",
1377
- "tray/items marked seen so the unseen count clears server-side",
1378
- "tray observer cleaned up on lifecycle end",
1378
+ "notifications.tray-live",
1379
+ "notifications.mark-seen",
1380
+ "notifications.preferences-respected",
1379
1381
  "this is in-app tray, not push setup",
1380
1382
  "validate_setup",
1381
1383
  "run_sensors",
@@ -16,7 +16,19 @@ export const PRODUCT_EXPECTATION_TITLES = {
16
16
  "chat.channel-shape-matched": "Chat channel type matches the requested shape",
17
17
  "chat.unread-visible": "Chat unread counts are visible",
18
18
  "chat.message-order-explicit": "Chat message order is explicit",
19
+ "community.target-resolved": "Community target comes from app state",
20
+ "community.members-live": "Community and member lists stay live",
21
+ "community.privacy-flow-explicit": "Community privacy flow is explicit",
22
+ "community.avatar-from-sdk": "Community avatars come from the SDK",
23
+ "community.display-name-from-sdk": "Community display names come from the SDK",
24
+ "moderation.role-gated-action": "Moderator-only actions are role gated",
25
+ "follow.target-resolved": "Follow target comes from app state",
26
+ "follow.relationship-live": "Follow relationships stay live",
27
+ "follow.model-explicit": "Follow approval model is explicit",
19
28
  "profile.social-counts": "Profile social counts come from the SDK",
29
+ "notifications.tray-live": "Notification tray is observed live",
30
+ "notifications.mark-seen": "Notification seen state clears server-side",
31
+ "notifications.preferences-respected": "Notification preferences are respected",
20
32
  };
21
33
  const platformBindings = (expectationId, sensorsByPlatform) => Object.entries(sensorsByPlatform).flatMap(([platform, sensors]) => (Array.isArray(sensors) ? sensors : [sensors]).map((sensorId) => ({
22
34
  expectationId,
@@ -358,6 +370,27 @@ export const PRODUCT_EXPECTATION_BINDINGS = [
358
370
  sensorId: "ios.chat.sort-explicit",
359
371
  platform: "ios",
360
372
  },
373
+ ...platformBindings("community.avatar-from-sdk", {
374
+ typescript: "typescript.community.avatar-from-sdk",
375
+ "react-native": "react-native.community.avatar-from-sdk",
376
+ android: "android.community.avatar-from-sdk",
377
+ flutter: "flutter.community.avatar-from-sdk",
378
+ ios: "ios.community.avatar-from-sdk",
379
+ }),
380
+ ...platformBindings("community.display-name-from-sdk", {
381
+ typescript: "typescript.community.display-name-from-sdk",
382
+ "react-native": "react-native.community.display-name-from-sdk",
383
+ android: "android.community.display-name-from-sdk",
384
+ flutter: "flutter.community.display-name-from-sdk",
385
+ ios: "ios.community.display-name-from-sdk",
386
+ }),
387
+ ...platformBindings("moderation.role-gated-action", {
388
+ typescript: "typescript.moderation.role-gated-action",
389
+ "react-native": "react-native.moderation.role-gated-action",
390
+ android: "android.moderation.role-gated-action",
391
+ flutter: "flutter.moderation.role-gated-action",
392
+ ios: "ios.moderation.role-gated-action",
393
+ }),
361
394
  {
362
395
  expectationId: "profile.social-counts",
363
396
  sensorId: "typescript.profile.social-counts-from-sdk",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amityco/social-plus-vise",
3
- "version": "0.14.19",
3
+ "version": "0.14.20",
4
4
  "description": "Skill-guided deterministic CLI for social.plus SDK integration assistance.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
package/rules/feed.yaml CHANGED
@@ -2595,6 +2595,7 @@
2595
2595
  "outcomes": [
2596
2596
  "add-feed",
2597
2597
  "add-comments",
2598
+ "add-community",
2598
2599
  "validate-setup"
2599
2600
  ]
2600
2601
  },
@@ -2632,6 +2633,7 @@
2632
2633
  "outcomes": [
2633
2634
  "add-feed",
2634
2635
  "add-comments",
2636
+ "add-community",
2635
2637
  "validate-setup"
2636
2638
  ]
2637
2639
  },
@@ -2669,6 +2671,7 @@
2669
2671
  "outcomes": [
2670
2672
  "add-feed",
2671
2673
  "add-comments",
2674
+ "add-community",
2672
2675
  "validate-setup"
2673
2676
  ]
2674
2677
  },
@@ -2706,6 +2709,7 @@
2706
2709
  "outcomes": [
2707
2710
  "add-feed",
2708
2711
  "add-comments",
2712
+ "add-community",
2709
2713
  "validate-setup"
2710
2714
  ]
2711
2715
  },
@@ -2743,6 +2747,7 @@
2743
2747
  "outcomes": [
2744
2748
  "add-feed",
2745
2749
  "add-comments",
2750
+ "add-community",
2746
2751
  "validate-setup"
2747
2752
  ]
2748
2753
  },
@@ -2837,7 +2842,7 @@
2837
2842
  "rationale": "Using a raw communityId as a displayName fallback (e.g. when passing only the ID in navigation state) shows a UUID to users instead of the community's actual name.",
2838
2843
  "applies_when": {
2839
2844
  "platforms": ["typescript"],
2840
- "outcomes": ["add-feed", "add-comments", "validate-setup"]
2845
+ "outcomes": ["add-feed", "add-comments", "add-community", "validate-setup"]
2841
2846
  },
2842
2847
  "enforcement": {
2843
2848
  "deterministic": [
@@ -2961,7 +2966,7 @@
2961
2966
  "rationale": "Using a raw communityId as a displayName fallback (e.g. when passing only the ID in navigation state) shows a UUID to users instead of the community's actual name.",
2962
2967
  "applies_when": {
2963
2968
  "platforms": ["react-native"],
2964
- "outcomes": ["add-feed", "add-comments", "validate-setup"]
2969
+ "outcomes": ["add-feed", "add-comments", "add-community", "validate-setup"]
2965
2970
  },
2966
2971
  "enforcement": {
2967
2972
  "deterministic": [
@@ -3085,7 +3090,7 @@
3085
3090
  "rationale": "Using a raw communityId as a displayName fallback (e.g. when passing only the ID in navigation state) shows a UUID to users instead of the community's actual name.",
3086
3091
  "applies_when": {
3087
3092
  "platforms": ["flutter"],
3088
- "outcomes": ["add-feed", "add-comments", "validate-setup"]
3093
+ "outcomes": ["add-feed", "add-comments", "add-community", "validate-setup"]
3089
3094
  },
3090
3095
  "enforcement": {
3091
3096
  "deterministic": [
@@ -3178,7 +3183,7 @@
3178
3183
  "rationale": "Using a raw communityId as a displayName fallback (e.g. when passing only the ID in navigation state) shows a UUID to users instead of the community's actual name.",
3179
3184
  "applies_when": {
3180
3185
  "platforms": ["ios"],
3181
- "outcomes": ["add-feed", "add-comments", "validate-setup"]
3186
+ "outcomes": ["add-feed", "add-comments", "add-community", "validate-setup"]
3182
3187
  },
3183
3188
  "enforcement": {
3184
3189
  "deterministic": [
@@ -3302,7 +3307,7 @@
3302
3307
  "rationale": "Using a raw communityId as a displayName fallback (e.g. when passing only the ID in navigation state) shows a UUID to users instead of the community's actual name.",
3303
3308
  "applies_when": {
3304
3309
  "platforms": ["android"],
3305
- "outcomes": ["add-feed", "add-comments", "validate-setup"]
3310
+ "outcomes": ["add-feed", "add-comments", "add-community", "validate-setup"]
3306
3311
  },
3307
3312
  "enforcement": {
3308
3313
  "deterministic": [
@@ -733,6 +733,7 @@
733
733
  "typescript"
734
734
  ],
735
735
  "outcomes": [
736
+ "add-community",
736
737
  "add-moderation",
737
738
  "validate-setup"
738
739
  ]
@@ -769,6 +770,7 @@
769
770
  "react-native"
770
771
  ],
771
772
  "outcomes": [
773
+ "add-community",
772
774
  "add-moderation",
773
775
  "validate-setup"
774
776
  ]
@@ -805,6 +807,7 @@
805
807
  "android"
806
808
  ],
807
809
  "outcomes": [
810
+ "add-community",
808
811
  "add-moderation",
809
812
  "validate-setup"
810
813
  ]
@@ -841,6 +844,7 @@
841
844
  "flutter"
842
845
  ],
843
846
  "outcomes": [
847
+ "add-community",
844
848
  "add-moderation",
845
849
  "validate-setup"
846
850
  ]
@@ -877,6 +881,7 @@
877
881
  "ios"
878
882
  ],
879
883
  "outcomes": [
884
+ "add-community",
880
885
  "add-moderation",
881
886
  "validate-setup"
882
887
  ]