@amityco/social-plus-vise 1.3.0 → 1.4.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +45 -2
  2. package/README.md +19 -9
  3. package/dist/capabilities.js +29 -1
  4. package/dist/entryState.js +71 -0
  5. package/dist/experience.js +70 -0
  6. package/dist/explore.js +1 -1
  7. package/dist/flow.js +382 -0
  8. package/dist/humanFormat.js +25 -0
  9. package/dist/intake.js +117 -0
  10. package/dist/intelligence/placement.js +2 -1
  11. package/dist/outcomes.js +126 -28
  12. package/dist/productExpectations.js +15 -0
  13. package/dist/requestReadiness.js +99 -0
  14. package/dist/server.js +566 -19
  15. package/dist/sidecar.js +5 -0
  16. package/dist/solutionPath.js +1 -1
  17. package/dist/tools/compliance.js +752 -125
  18. package/dist/tools/creative.js +14 -12
  19. package/dist/tools/design.js +321 -11
  20. package/dist/tools/experienceCompiler.js +1 -1
  21. package/dist/tools/experienceSensors.js +1 -1
  22. package/dist/tools/harness.js +13 -0
  23. package/dist/tools/integration.js +263 -90
  24. package/dist/tools/learning.js +1 -3
  25. package/dist/tools/project.js +963 -66
  26. package/dist/tools/smoke.js +134 -0
  27. package/dist/tools/uxHarness.js +54 -6
  28. package/dist/uikitCustomization.js +3 -1
  29. package/dist/version.js +7 -3
  30. package/package.json +1 -1
  31. package/packages/intelligence/catalog/catalog.schema.json +1 -1
  32. package/packages/intelligence/catalog/experience-objects.json +2 -2
  33. package/packages/intelligence/catalog/variants.json +24 -0
  34. package/rules/event.yaml +3 -0
  35. package/rules/feed.yaml +251 -0
  36. package/rules/invitation.yaml +4 -0
  37. package/rules/live-data.yaml +110 -0
  38. package/rules/notification-tray.yaml +4 -0
  39. package/rules/poll.yaml +5 -0
  40. package/rules/sdk-lifecycle.yaml +559 -0
  41. package/rules/search.yaml +5 -0
  42. package/rules/security.yaml +12 -0
  43. package/rules/story.yaml +5 -0
  44. package/rules/user-blocking.yaml +5 -0
  45. package/skills/social-plus-vise/SKILL.md +163 -15
package/rules/feed.yaml CHANGED
@@ -2,6 +2,72 @@
2
2
  "domain": "feed",
3
3
  "schema_version": 1,
4
4
  "rules": [
5
+ {
6
+ "id": "typescript.feed.source-used",
7
+ "version": 1,
8
+ "title": "TypeScript feed integration must use SDK post APIs",
9
+ "severity": "warning",
10
+ "rationale": "A community/profile-only SDK integration is not an add-feed implementation. Feed surfaces must query or create SDK posts and render SDK post data.",
11
+ "applies_when": {
12
+ "platforms": ["typescript"],
13
+ "outcomes": ["add-feed", "validate-setup"]
14
+ },
15
+ "enforcement": {
16
+ "deterministic": [
17
+ { "check": "validator-finding-absent", "finding_rule_id": "typescript.feed.source-used" }
18
+ ],
19
+ "attestation": {
20
+ "allowed": false,
21
+ "host_agent_min_confidence": "high",
22
+ "human_allowed": false,
23
+ "evidence_required": []
24
+ }
25
+ }
26
+ },
27
+ {
28
+ "id": "react-native.feed.source-used",
29
+ "version": 1,
30
+ "title": "React Native feed integration must use SDK post APIs",
31
+ "severity": "warning",
32
+ "rationale": "A community/profile-only SDK integration is not an add-feed implementation. Feed surfaces must query or create SDK posts and render SDK post data.",
33
+ "applies_when": {
34
+ "platforms": ["react-native"],
35
+ "outcomes": ["add-feed", "validate-setup"]
36
+ },
37
+ "enforcement": {
38
+ "deterministic": [
39
+ { "check": "validator-finding-absent", "finding_rule_id": "react-native.feed.source-used" }
40
+ ],
41
+ "attestation": {
42
+ "allowed": false,
43
+ "host_agent_min_confidence": "high",
44
+ "human_allowed": false,
45
+ "evidence_required": []
46
+ }
47
+ }
48
+ },
49
+ {
50
+ "id": "flutter.feed.source-used",
51
+ "version": 1,
52
+ "title": "Flutter feed integration must use SDK post APIs",
53
+ "severity": "warning",
54
+ "rationale": "A community/profile-only SDK integration is not an add-feed implementation. Feed surfaces must query or create SDK posts and render SDK post data.",
55
+ "applies_when": {
56
+ "platforms": ["flutter"],
57
+ "outcomes": ["add-feed", "validate-setup"]
58
+ },
59
+ "enforcement": {
60
+ "deterministic": [
61
+ { "check": "validator-finding-absent", "finding_rule_id": "flutter.feed.source-used" }
62
+ ],
63
+ "attestation": {
64
+ "allowed": false,
65
+ "host_agent_min_confidence": "high",
66
+ "human_allowed": false,
67
+ "evidence_required": []
68
+ }
69
+ }
70
+ },
5
71
  {
6
72
  "id": "typescript.feed.target.literal",
7
73
  "version": 1,
@@ -1292,6 +1358,191 @@
1292
1358
  }
1293
1359
  }
1294
1360
  },
1361
+ {
1362
+ "id": "typescript.feed.post-body-rendered",
1363
+ "version": 1,
1364
+ "symbol_anchored": {"platform":"typescript","basis":"pattern"},
1365
+ "title": "TypeScript post cards must render the post body when present",
1366
+ "severity": "warning",
1367
+ "rationale": "A feed card that shows only postId, counts, or chrome hides the user's actual post. Render the normal body/description/caption when present. A title field is optional and metadata/custom title payloads are not required.",
1368
+ "applies_when": {
1369
+ "platforms": [
1370
+ "typescript"
1371
+ ],
1372
+ "outcomes": [
1373
+ "add-feed",
1374
+ "validate-setup"
1375
+ ]
1376
+ },
1377
+ "enforcement": {
1378
+ "deterministic": [
1379
+ {
1380
+ "check": "validator-finding-absent",
1381
+ "finding_rule_id": "typescript.feed.post-body-rendered"
1382
+ }
1383
+ ],
1384
+ "attestation": {
1385
+ "allowed": true,
1386
+ "host_agent_min_confidence": "high",
1387
+ "human_allowed": true,
1388
+ "evidence_required": [
1389
+ {
1390
+ "field": "post_body_rendering",
1391
+ "description": "Where the post body/description/caption is read from the SDK object and rendered. Do not use title/metadata/custom payload as required evidence.",
1392
+ "upload_policy": "upload-with-consent"
1393
+ }
1394
+ ]
1395
+ }
1396
+ }
1397
+ },
1398
+ {
1399
+ "id": "react-native.feed.post-body-rendered",
1400
+ "version": 1,
1401
+ "symbol_anchored": {"platform":"react-native","basis":"pattern"},
1402
+ "title": "React Native post cards must render the post body when present",
1403
+ "severity": "warning",
1404
+ "rationale": "A feed card that shows only postId, counts, or chrome hides the user's actual post. Render the normal body/description/caption when present. A title field is optional and metadata/custom title payloads are not required.",
1405
+ "applies_when": {
1406
+ "platforms": [
1407
+ "react-native"
1408
+ ],
1409
+ "outcomes": [
1410
+ "add-feed",
1411
+ "validate-setup"
1412
+ ]
1413
+ },
1414
+ "enforcement": {
1415
+ "deterministic": [
1416
+ {
1417
+ "check": "validator-finding-absent",
1418
+ "finding_rule_id": "react-native.feed.post-body-rendered"
1419
+ }
1420
+ ],
1421
+ "attestation": {
1422
+ "allowed": true,
1423
+ "host_agent_min_confidence": "high",
1424
+ "human_allowed": true,
1425
+ "evidence_required": [
1426
+ {
1427
+ "field": "post_body_rendering",
1428
+ "description": "Where the post body/description/caption is read from the SDK object and rendered. Do not use title/metadata/custom payload as required evidence.",
1429
+ "upload_policy": "upload-with-consent"
1430
+ }
1431
+ ]
1432
+ }
1433
+ }
1434
+ },
1435
+ {
1436
+ "id": "android.feed.post-body-rendered",
1437
+ "version": 1,
1438
+ "symbol_anchored": {"platform":"android","basis":"pattern"},
1439
+ "title": "Android post cards must render the post body when present",
1440
+ "severity": "warning",
1441
+ "rationale": "A feed card that shows only postId, counts, or chrome hides the user's actual post. Render the normal body/description/caption when present. A title field is optional and metadata/custom title payloads are not required.",
1442
+ "applies_when": {
1443
+ "platforms": [
1444
+ "android"
1445
+ ],
1446
+ "outcomes": [
1447
+ "add-feed",
1448
+ "validate-setup"
1449
+ ]
1450
+ },
1451
+ "enforcement": {
1452
+ "deterministic": [
1453
+ {
1454
+ "check": "validator-finding-absent",
1455
+ "finding_rule_id": "android.feed.post-body-rendered"
1456
+ }
1457
+ ],
1458
+ "attestation": {
1459
+ "allowed": true,
1460
+ "host_agent_min_confidence": "high",
1461
+ "human_allowed": true,
1462
+ "evidence_required": [
1463
+ {
1464
+ "field": "post_body_rendering",
1465
+ "description": "Where the post body/description/caption is read from the SDK object and rendered. Do not use title/metadata/custom payload as required evidence.",
1466
+ "upload_policy": "upload-with-consent"
1467
+ }
1468
+ ]
1469
+ }
1470
+ }
1471
+ },
1472
+ {
1473
+ "id": "flutter.feed.post-body-rendered",
1474
+ "version": 1,
1475
+ "symbol_anchored": {"platform":"flutter","basis":"pattern"},
1476
+ "title": "Flutter post cards must render the post body when present",
1477
+ "severity": "warning",
1478
+ "rationale": "A feed card that shows only postId, counts, or chrome hides the user's actual post. Render the normal body/description/caption when present. A title field is optional and metadata/custom title payloads are not required.",
1479
+ "applies_when": {
1480
+ "platforms": [
1481
+ "flutter"
1482
+ ],
1483
+ "outcomes": [
1484
+ "add-feed",
1485
+ "validate-setup"
1486
+ ]
1487
+ },
1488
+ "enforcement": {
1489
+ "deterministic": [
1490
+ {
1491
+ "check": "validator-finding-absent",
1492
+ "finding_rule_id": "flutter.feed.post-body-rendered"
1493
+ }
1494
+ ],
1495
+ "attestation": {
1496
+ "allowed": true,
1497
+ "host_agent_min_confidence": "high",
1498
+ "human_allowed": true,
1499
+ "evidence_required": [
1500
+ {
1501
+ "field": "post_body_rendering",
1502
+ "description": "Where the post body/description/caption is read from the SDK object and rendered. Do not use title/metadata/custom payload as required evidence.",
1503
+ "upload_policy": "upload-with-consent"
1504
+ }
1505
+ ]
1506
+ }
1507
+ }
1508
+ },
1509
+ {
1510
+ "id": "ios.feed.post-body-rendered",
1511
+ "version": 1,
1512
+ "symbol_anchored": {"platform":"ios","basis":"pattern"},
1513
+ "title": "iOS post cards must render the post body when present",
1514
+ "severity": "warning",
1515
+ "rationale": "A feed card that shows only postId, counts, or chrome hides the user's actual post. Render the normal body/description/caption when present. A title field is optional and metadata/custom title payloads are not required.",
1516
+ "applies_when": {
1517
+ "platforms": [
1518
+ "ios"
1519
+ ],
1520
+ "outcomes": [
1521
+ "add-feed",
1522
+ "validate-setup"
1523
+ ]
1524
+ },
1525
+ "enforcement": {
1526
+ "deterministic": [
1527
+ {
1528
+ "check": "validator-finding-absent",
1529
+ "finding_rule_id": "ios.feed.post-body-rendered"
1530
+ }
1531
+ ],
1532
+ "attestation": {
1533
+ "allowed": true,
1534
+ "host_agent_min_confidence": "high",
1535
+ "human_allowed": true,
1536
+ "evidence_required": [
1537
+ {
1538
+ "field": "post_body_rendering",
1539
+ "description": "Where the post body/description/caption is read from the SDK object and rendered. Do not use title/metadata/custom payload as required evidence.",
1540
+ "upload_policy": "upload-with-consent"
1541
+ }
1542
+ ]
1543
+ }
1544
+ }
1545
+ },
1295
1546
  {
1296
1547
  "id": "typescript.feed.target-type-explicit",
1297
1548
  "version": 1,
@@ -5,6 +5,7 @@
5
5
  {
6
6
  "id": "typescript.invitation.live-collection",
7
7
  "version": 1,
8
+ "symbol_anchored": { "platform": "typescript", "types": ["InvitationRepository"], "members": ["getMyCommunityInvitations"], "basis": "names-only" },
8
9
  "title": "TypeScript community-invitations list should use the reactive getMyCommunityInvitations LiveCollection",
9
10
  "severity": "warning",
10
11
  "rationale": "Community invitation lists are Live Collections (Amity.InvitationLiveCollection). An invitations screen rendered from a one-shot fetch (no callback) never refreshes as invitations arrive or transition pending -> accepted/rejected/cancelled. Pass the live callback: getMyCommunityInvitations(params, callback). If a static one-shot snapshot is truly intended, add // vise: one-shot query.",
@@ -18,6 +19,7 @@
18
19
  {
19
20
  "id": "react-native.invitation.live-collection",
20
21
  "version": 1,
22
+ "symbol_anchored": { "platform": "react-native", "types": ["InvitationRepository"], "members": ["getMyCommunityInvitations"], "basis": "names-only" },
21
23
  "title": "React Native community-invitations list should use the reactive getMyCommunityInvitations LiveCollection",
22
24
  "severity": "warning",
23
25
  "rationale": "Community invitation lists are Live Collections. An invitations screen rendered from a one-shot fetch (no callback) never refreshes as invitations arrive or transition pending -> accepted/rejected/cancelled. Pass the live callback: getMyCommunityInvitations(params, callback). If a static one-shot snapshot is truly intended, add // vise: one-shot query.",
@@ -31,6 +33,7 @@
31
33
  {
32
34
  "id": "android.invitation.live-collection",
33
35
  "version": 1,
36
+ "symbol_anchored": { "platform": "android", "types": ["AmityInvitationRepository"], "members": ["getMyCommunityInvitations"], "basis": "names-only" },
34
37
  "title": "Android community-invitations list should observe the invitation PagingData stream",
35
38
  "severity": "warning",
36
39
  "rationale": "Community invitation lists are Live Collections (getMyCommunityInvitations / getMemberInvitations return Flowable<PagingData<AmityInvitation>>). An invitations screen rendered from a one-shot fetch never refreshes as invitations arrive or change status. Observe the stream instead: getMyCommunityInvitations().doOnNext { _: PagingData -> }.subscribe(). If a static one-shot snapshot is truly intended, add // vise: one-shot query.",
@@ -44,6 +47,7 @@
44
47
  {
45
48
  "id": "ios.invitation.live-collection",
46
49
  "version": 1,
50
+ "symbol_anchored": { "platform": "ios", "types": ["AmityInvitationRepository"], "members": ["getMyCommunityInvitations"], "basis": "names-only" },
47
51
  "title": "iOS community-invitations list should observe the invitation LiveCollection",
48
52
  "severity": "warning",
49
53
  "rationale": "Community invitation lists are Live Collections (getMyCommunityInvitations / getMemberInvitations return AmityCollection<AmityInvitation>). An invitations screen rendered from a one-shot snapshot never refreshes as invitations arrive or change status. Observe the collection instead: getMyCommunityInvitations().observe { ... }. If a static one-shot snapshot is truly intended, add // vise: one-shot query.",
@@ -503,6 +503,116 @@
503
503
  ]
504
504
  }
505
505
  }
506
+ },
507
+ {
508
+ "id": "android.live-collection.session-ready-requery",
509
+ "version": 1,
510
+ "title": "Android live queries must (re-)run when the session becomes active after the UI mounts",
511
+ "severity": "warning",
512
+ "rationale": "The screen that consumes live data usually mounts before login resolves. If the live query is gated on a one-shot session-readiness check at mount (onCreate / onViewCreated / a no-key LaunchedEffect) that returns early and never re-fires, the surface stays permanently empty even though it compiles and the build is green. On a warm device login resolves first by chance; on a cold start the check fires too early and nothing re-runs the query.",
513
+ "feedforward": "Drive the live query off a REACTIVE session-readiness signal — observe the session/auth state (e.g. a StateFlow) and seed it synchronously from its current value — so the query (re-)runs when the session becomes active, whether that is before or after the view mounted. Do not gate it on a one-shot readiness check at mount (onCreate / onViewCreated / no-key LaunchedEffect) that never re-fires. Render the not-ready and error states as retryable (a visible retry affordance), never a silent dead end.",
514
+ "applies_when": {
515
+ "platforms": ["android"],
516
+ "outcomes": ["add-feed", "add-comments", "add-chat", "setup-live-data", "validate-setup"]
517
+ },
518
+ "enforcement": {
519
+ "deterministic": [
520
+ { "check": "runtime-smoke-evidence-passed", "path": "sp-vise/evidence/runtime-smoke.json" }
521
+ ],
522
+ "attestation": {
523
+ "allowed": false,
524
+ "human_allowed": false,
525
+ "evidence_required": []
526
+ }
527
+ }
528
+ },
529
+ {
530
+ "id": "flutter.live-collection.session-ready-requery",
531
+ "version": 1,
532
+ "title": "Flutter live queries must (re-)run when the session becomes active after the UI mounts",
533
+ "severity": "warning",
534
+ "rationale": "The widget that consumes live data usually mounts before login resolves. If the live query is gated on a one-shot session-readiness check at mount (initState) that returns early and never re-fires, the surface stays permanently empty even though it compiles and the build is green. On a warm device login resolves first by chance; on a cold start the check fires too early and nothing re-runs the query.",
535
+ "feedforward": "Drive the live query off a REACTIVE session-readiness signal — observe the session/auth state (e.g. a Stream/StateNotifier) and seed it synchronously from its current value — so the query (re-)runs when the session becomes active, whether that is before or after the widget mounted. Do not gate it on a one-shot readiness check in initState that never re-fires. Render the not-ready and error states as retryable (a visible retry affordance), never a silent dead end.",
536
+ "applies_when": {
537
+ "platforms": ["flutter"],
538
+ "outcomes": ["add-feed", "add-comments", "add-chat", "setup-live-data", "validate-setup"]
539
+ },
540
+ "enforcement": {
541
+ "deterministic": [
542
+ { "check": "runtime-smoke-evidence-passed", "path": "sp-vise/evidence/runtime-smoke.json" }
543
+ ],
544
+ "attestation": {
545
+ "allowed": false,
546
+ "human_allowed": false,
547
+ "evidence_required": []
548
+ }
549
+ }
550
+ },
551
+ {
552
+ "id": "ios.live-collection.session-ready-requery",
553
+ "version": 1,
554
+ "title": "iOS live queries must (re-)run when the session becomes active after the UI mounts",
555
+ "severity": "warning",
556
+ "rationale": "The view that consumes live data usually mounts before login resolves. If the live query is gated on a one-shot session-readiness check at mount (.onAppear / viewDidLoad) that returns early and never re-fires, the surface stays permanently empty even though it compiles and the build is green. On a warm machine login resolves first by chance; on a cold start the check fires too early and nothing re-runs the query.",
557
+ "feedforward": "Drive the live query off a REACTIVE session-readiness signal — observe the SDK session state (e.g. an @Published value sink on the client's session state) and seed it synchronously from its current value — so the query (re-)runs when the session becomes active, whether that is before or after the view appeared. Do not gate it on a one-shot isReady read in .onAppear that never re-fires. Render the not-ready and error states as retryable (a visible retry affordance), never a silent dead end.",
558
+ "applies_when": {
559
+ "platforms": ["ios"],
560
+ "outcomes": ["add-feed", "add-comments", "add-chat", "setup-live-data", "validate-setup"]
561
+ },
562
+ "enforcement": {
563
+ "deterministic": [
564
+ { "check": "runtime-smoke-evidence-passed", "path": "sp-vise/evidence/runtime-smoke.json" }
565
+ ],
566
+ "attestation": {
567
+ "allowed": false,
568
+ "human_allowed": false,
569
+ "evidence_required": []
570
+ }
571
+ }
572
+ },
573
+ {
574
+ "id": "typescript.live-collection.session-ready-requery",
575
+ "version": 1,
576
+ "title": "TypeScript live queries must (re-)run when the session becomes active after the component mounts",
577
+ "severity": "warning",
578
+ "rationale": "The component that consumes live data usually mounts before login resolves. If the live query is gated on a one-shot session-readiness check at mount (useEffect([]) or a single read of client.sessionState) that returns early and never re-fires, the surface stays permanently empty even though it compiles and the build is green. On a warm machine login resolves first by chance; on a cold start the check fires too early and nothing re-runs the query.",
579
+ "feedforward": "Drive the live query off a REACTIVE session-readiness signal — subscribe to the client session state AND seed it synchronously (e.g. useState(client.sessionState) so an already-established session is not missed) — so the query (re-)runs when the session becomes active, whether that is before or after the component mounted. Do not gate it on a single read of client.sessionState in a mount-only useEffect that never re-fires. Render the not-ready and error states as retryable (a visible retry affordance), never a silent dead end.",
580
+ "applies_when": {
581
+ "platforms": ["typescript"],
582
+ "outcomes": ["add-feed", "add-comments", "add-chat", "setup-live-data", "validate-setup"]
583
+ },
584
+ "enforcement": {
585
+ "deterministic": [
586
+ { "check": "runtime-smoke-evidence-passed", "path": "sp-vise/evidence/runtime-smoke.json" }
587
+ ],
588
+ "attestation": {
589
+ "allowed": false,
590
+ "human_allowed": false,
591
+ "evidence_required": []
592
+ }
593
+ }
594
+ },
595
+ {
596
+ "id": "react-native.live-collection.session-ready-requery",
597
+ "version": 1,
598
+ "title": "React Native live queries must (re-)run when the session becomes active after the component mounts",
599
+ "severity": "warning",
600
+ "rationale": "The screen that consumes live data usually mounts before login resolves. If the live query is gated on a one-shot session-readiness check at mount (useEffect([]) or a single read of client.sessionState) that returns early and never re-fires, the surface stays permanently empty even though it compiles and the build is green. On a warm device login resolves first by chance; on a cold start the check fires too early and nothing re-runs the query.",
601
+ "feedforward": "Drive the live query off a REACTIVE session-readiness signal — subscribe to the client session state AND seed it synchronously (e.g. useState(client.sessionState) so an already-established session is not missed) — so the query (re-)runs when the session becomes active, whether that is before or after the screen mounted. Do not gate it on a single read of client.sessionState in a mount-only useEffect that never re-fires. Render the not-ready and error states as retryable (a visible retry affordance), never a silent dead end.",
602
+ "applies_when": {
603
+ "platforms": ["react-native"],
604
+ "outcomes": ["add-feed", "add-comments", "add-chat", "setup-live-data", "validate-setup"]
605
+ },
606
+ "enforcement": {
607
+ "deterministic": [
608
+ { "check": "runtime-smoke-evidence-passed", "path": "sp-vise/evidence/runtime-smoke.json" }
609
+ ],
610
+ "attestation": {
611
+ "allowed": false,
612
+ "human_allowed": false,
613
+ "evidence_required": []
614
+ }
615
+ }
506
616
  }
507
617
  ]
508
618
  }
@@ -5,6 +5,7 @@
5
5
  {
6
6
  "id": "typescript.notification-tray.live-collection",
7
7
  "version": 1,
8
+ "symbol_anchored": { "platform": "typescript", "types": ["notificationTray"], "members": ["getNotificationTrayItems"], "basis": "names-only" },
8
9
  "title": "TypeScript notification tray should use the reactive Tray LiveCollection",
9
10
  "severity": "warning",
10
11
  "rationale": "The notification tray is a Live Collection — it updates as notifications arrive and seen-state changes. A one-shot getNotificationTrayItems (no callback) renders a tray that never updates. Pass the live callback: notificationTray.getNotificationTrayItems(params, callback).",
@@ -30,6 +31,7 @@
30
31
  {
31
32
  "id": "react-native.notification-tray.live-collection",
32
33
  "version": 1,
34
+ "symbol_anchored": { "platform": "react-native", "types": ["notificationTray"], "members": ["getNotificationTrayItems"], "basis": "names-only" },
33
35
  "title": "React Native notification tray should use the reactive Tray LiveCollection",
34
36
  "severity": "warning",
35
37
  "rationale": "The notification tray is a Live Collection — it updates as notifications arrive and seen-state changes. A one-shot getNotificationTrayItems (no callback) renders a tray that never updates. Pass the live callback: notificationTray.getNotificationTrayItems(params, callback).",
@@ -55,6 +57,7 @@
55
57
  {
56
58
  "id": "android.notification-tray.live-collection",
57
59
  "version": 1,
60
+ "symbol_anchored": { "platform": "android", "types": ["AmityNotificationTray"], "members": ["getNotificationTrayItems"], "basis": "names-only" },
58
61
  "title": "Android notification tray should use the reactive Tray LiveCollection",
59
62
  "severity": "warning",
60
63
  "rationale": "The notification tray is a Live Collection — it updates as notifications arrive and seen-state changes. A one-shot fetch renders a tray that never updates. Observe the collection instead (PagingData via .doOnNext { ... }.subscribe()).",
@@ -80,6 +83,7 @@
80
83
  {
81
84
  "id": "ios.notification-tray.live-collection",
82
85
  "version": 1,
86
+ "symbol_anchored": { "platform": "ios", "types": ["AmityNotificationTray"], "members": ["getNotificationTrayItems"], "basis": "names-only" },
83
87
  "title": "iOS notification tray should use the reactive Tray LiveCollection",
84
88
  "severity": "warning",
85
89
  "rationale": "The notification tray is a Live Collection — it updates as notifications arrive and seen-state changes. A one-shot completion-handler fetch renders a tray that never updates. Observe the collection instead: getNotificationTrayItems(...).observe { ... }.",
package/rules/poll.yaml CHANGED
@@ -5,6 +5,7 @@
5
5
  {
6
6
  "id": "typescript.poll.vote-status-guard",
7
7
  "version": 1,
8
+ "symbol_anchored": { "platform": "typescript", "types": ["PollRepository"], "members": ["votePoll"], "basis": "names-only" },
8
9
  "title": "TypeScript poll voting must check poll status / voted-state before allowing a vote",
9
10
  "severity": "warning",
10
11
  "rationale": "A poll renderer that wires votePoll for each answer but never reads poll.status / poll.isVoted / answer.voteCount lets the user vote on a closed poll (silent backend reject), re-vote without feedback, and shows no results. Gate the vote on poll.status and poll.isVoted, and render answer.voteCount / answer.isVotedByUser.",
@@ -18,6 +19,7 @@
18
19
  {
19
20
  "id": "react-native.poll.vote-status-guard",
20
21
  "version": 1,
22
+ "symbol_anchored": { "platform": "react-native", "types": ["PollRepository"], "members": ["votePoll"], "basis": "names-only" },
21
23
  "title": "React Native poll voting must check poll status / voted-state before allowing a vote",
22
24
  "severity": "warning",
23
25
  "rationale": "A poll renderer that wires votePoll for each answer but never reads poll.status / poll.isVoted / answer.voteCount lets the user vote on a closed poll (silent backend reject), re-vote without feedback, and shows no results. Gate the vote on poll.status and poll.isVoted, and render answer.voteCount / answer.isVotedByUser.",
@@ -31,6 +33,7 @@
31
33
  {
32
34
  "id": "android.poll.vote-status-guard",
33
35
  "version": 1,
36
+ "symbol_anchored": { "platform": "android", "types": ["AmityPollRepository"], "members": ["votePoll"], "basis": "names-only" },
34
37
  "title": "Android poll voting must check poll status / voted-state before allowing a vote",
35
38
  "severity": "warning",
36
39
  "rationale": "A poll renderer that wires votePoll for each answer but never reads poll.getStatus() / poll.isVoted / answer.voteCount lets the user vote on a closed poll (silent backend reject), re-vote without feedback, and shows no results. Gate the vote on poll status and isVoted, and render answer.voteCount / answer.isVotedByUser.",
@@ -44,6 +47,7 @@
44
47
  {
45
48
  "id": "ios.poll.vote-status-guard",
46
49
  "version": 1,
50
+ "symbol_anchored": { "platform": "ios", "types": ["AmityPollRepository"], "members": ["votePoll"], "basis": "names-only" },
47
51
  "title": "iOS poll voting must check poll status / voted-state before allowing a vote",
48
52
  "severity": "warning",
49
53
  "rationale": "A poll renderer that wires votePoll for each answer but never reads poll.isClosed / poll.isVoted / poll.voteCount lets the user vote on a closed poll (silent backend reject), re-vote without feedback, and shows no results. Gate the vote on poll.isClosed and poll.isVoted, and render answer.voteCount / answer.isVotedByUser.",
@@ -57,6 +61,7 @@
57
61
  {
58
62
  "id": "flutter.poll.vote-status-guard",
59
63
  "version": 1,
64
+ "symbol_anchored": { "platform": "flutter", "types": ["PollRepository"], "members": ["vote"], "basis": "names-only" },
60
65
  "title": "Flutter poll voting must check poll status / voted-state before allowing a vote",
61
66
  "severity": "warning",
62
67
  "rationale": "A poll renderer that wires vote(pollId:) for each answer but never reads poll.status / poll.isVoted / answer.voteCount lets the user vote on a closed poll (silent backend reject), re-vote without feedback, and shows no results. Gate the vote on poll.status (AmityPollStatus) and poll.isVoted, and render answer.voteCount / answer.isVotedByUser.",