@amityco/social-plus-vise 1.3.0 → 1.4.1
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 +51 -2
- package/README.md +36 -19
- package/dist/capabilities.js +29 -1
- package/dist/entryState.js +71 -0
- package/dist/experience.js +70 -0
- package/dist/explore.js +1 -1
- package/dist/flow.js +382 -0
- package/dist/humanFormat.js +25 -0
- package/dist/intake.js +117 -0
- package/dist/intelligence/placement.js +2 -1
- package/dist/outcomes.js +126 -28
- package/dist/productExpectations.js +15 -0
- package/dist/requestReadiness.js +99 -0
- package/dist/server.js +566 -19
- package/dist/sidecar.js +5 -0
- package/dist/solutionPath.js +1 -1
- package/dist/tools/compliance.js +752 -125
- package/dist/tools/creative.js +14 -12
- package/dist/tools/design.js +321 -11
- package/dist/tools/experienceCompiler.js +1 -1
- package/dist/tools/experienceSensors.js +1 -1
- package/dist/tools/harness.js +13 -0
- package/dist/tools/integration.js +263 -90
- package/dist/tools/learning.js +1 -3
- package/dist/tools/project.js +963 -66
- package/dist/tools/smoke.js +134 -0
- package/dist/tools/uxHarness.js +54 -6
- package/dist/uikitCustomization.js +3 -1
- package/dist/version.js +7 -3
- package/package.json +1 -1
- package/packages/intelligence/catalog/catalog.schema.json +1 -1
- package/packages/intelligence/catalog/experience-objects.json +2 -2
- package/packages/intelligence/catalog/variants.json +24 -0
- package/rules/event.yaml +3 -0
- package/rules/feed.yaml +251 -0
- package/rules/invitation.yaml +4 -0
- package/rules/live-data.yaml +110 -0
- package/rules/notification-tray.yaml +4 -0
- package/rules/poll.yaml +5 -0
- package/rules/sdk-lifecycle.yaml +559 -0
- package/rules/search.yaml +5 -0
- package/rules/security.yaml +12 -0
- package/rules/story.yaml +5 -0
- package/rules/user-blocking.yaml +5 -0
- package/skills/social-plus-vise/SKILL.md +163 -15
package/rules/story.yaml
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
{
|
|
6
6
|
"id": "typescript.story.live-collection",
|
|
7
7
|
"version": 1,
|
|
8
|
+
"symbol_anchored": { "platform": "typescript", "types": ["StoryRepository"], "members": ["getActiveStoriesByTarget"], "basis": "names-only" },
|
|
8
9
|
"title": "TypeScript story trays should use the reactive Story LiveCollection",
|
|
9
10
|
"severity": "warning",
|
|
10
11
|
"rationale": "Story trays are Live Collections \u2014 they update as stories are created, viewed, expire, or sync. A one-shot Promise-style getActiveStoriesByTarget (no callback) renders a tray that never updates. Pass the live callback: StoryRepository.getActiveStoriesByTarget(params, callback).",
|
|
@@ -48,6 +49,7 @@
|
|
|
48
49
|
{
|
|
49
50
|
"id": "react-native.story.live-collection",
|
|
50
51
|
"version": 1,
|
|
52
|
+
"symbol_anchored": { "platform": "react-native", "types": ["StoryRepository"], "members": ["getActiveStoriesByTarget"], "basis": "names-only" },
|
|
51
53
|
"title": "React Native story trays should use the reactive Story LiveCollection",
|
|
52
54
|
"severity": "warning",
|
|
53
55
|
"rationale": "Story trays are Live Collections \u2014 they update as stories are created, viewed, expire, or sync. A one-shot Promise-style getActiveStoriesByTarget (no callback) renders a tray that never updates. Pass the live callback: StoryRepository.getActiveStoriesByTarget(params, callback).",
|
|
@@ -91,6 +93,7 @@
|
|
|
91
93
|
{
|
|
92
94
|
"id": "android.story.live-collection",
|
|
93
95
|
"version": 1,
|
|
96
|
+
"symbol_anchored": { "platform": "android", "types": ["AmityStoryRepository"], "members": ["getActiveStories"], "basis": "names-only" },
|
|
94
97
|
"title": "Android story trays should use the reactive Story LiveCollection",
|
|
95
98
|
"severity": "warning",
|
|
96
99
|
"rationale": "Story trays are Live Collections \u2014 they update as stories are created, viewed, expire, or sync. A one-shot Future-style getActiveStories (.execute()/single fetch) renders a tray that never updates. Observe the collection instead (PagingData via .doOnNext { ... }.subscribe()).",
|
|
@@ -134,6 +137,7 @@
|
|
|
134
137
|
{
|
|
135
138
|
"id": "ios.story.live-collection",
|
|
136
139
|
"version": 1,
|
|
140
|
+
"symbol_anchored": { "platform": "ios", "types": ["AmityStoryRepository"], "members": ["getActiveStoriesByTarget"], "basis": "names-only" },
|
|
137
141
|
"title": "iOS story trays should use the reactive Story LiveCollection",
|
|
138
142
|
"severity": "warning",
|
|
139
143
|
"rationale": "Story trays are Live Collections \u2014 they update as stories are created, viewed, expire, or sync. A one-shot completion-handler getActiveStoriesByTarget renders a tray that never updates. Observe the collection instead: getActiveStoriesByTarget(...).observe { ... }.",
|
|
@@ -177,6 +181,7 @@
|
|
|
177
181
|
{
|
|
178
182
|
"id": "flutter.story.live-collection",
|
|
179
183
|
"version": 1,
|
|
184
|
+
"symbol_anchored": { "platform": "flutter", "types": ["AmityStoryRepository"], "members": ["getActiveStories"], "basis": "names-only" },
|
|
180
185
|
"title": "Flutter story trays should use the reactive Story LiveCollection",
|
|
181
186
|
"severity": "warning",
|
|
182
187
|
"rationale": "Story trays are Live Collections \u2014 they update as stories are created, viewed, expire, or sync. A one-shot Future-style getActiveStories (.query()/await) renders a tray that never updates. Observe the collection instead (StoryLiveCollection().getStreamController().stream.listen(...)).",
|
package/rules/user-blocking.yaml
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
{
|
|
6
6
|
"id": "typescript.blocked-users.live-collection",
|
|
7
7
|
"version": 1,
|
|
8
|
+
"symbol_anchored": { "platform": "typescript", "types": ["UserRepository"], "members": ["getBlockedUsers"], "basis": "names-only" },
|
|
8
9
|
"title": "TypeScript blocked-users list should use the reactive getBlockedUsers LiveCollection",
|
|
9
10
|
"severity": "warning",
|
|
10
11
|
"rationale": "The blocked-users list is a Live Collection. A 'Manage Blocked Users' screen rendered from a one-shot fetch (no callback) does not refresh when the user taps unblock on that same screen. Pass the live callback: UserRepository.getBlockedUsers(params, callback). For a one-shot feed-filter snapshot, use getAllBlockedUsers instead.",
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
{
|
|
19
20
|
"id": "react-native.blocked-users.live-collection",
|
|
20
21
|
"version": 1,
|
|
22
|
+
"symbol_anchored": { "platform": "react-native", "types": ["UserRepository"], "members": ["getBlockedUsers"], "basis": "names-only" },
|
|
21
23
|
"title": "React Native blocked-users list should use the reactive getBlockedUsers LiveCollection",
|
|
22
24
|
"severity": "warning",
|
|
23
25
|
"rationale": "The blocked-users list is a Live Collection. A 'Manage Blocked Users' screen rendered from a one-shot fetch (no callback) does not refresh when the user taps unblock on that same screen. Pass the live callback: UserRepository.getBlockedUsers(params, callback). For a one-shot feed-filter snapshot, use getAllBlockedUsers instead.",
|
|
@@ -31,6 +33,7 @@
|
|
|
31
33
|
{
|
|
32
34
|
"id": "android.blocked-users.live-collection",
|
|
33
35
|
"version": 1,
|
|
36
|
+
"symbol_anchored": { "platform": "android", "types": ["AmityUserRepository"], "members": ["getBlockedUsers"], "basis": "names-only" },
|
|
34
37
|
"title": "Android blocked-users list should use the reactive getBlockedUsers LiveCollection",
|
|
35
38
|
"severity": "warning",
|
|
36
39
|
"rationale": "The blocked-users list is a Live Collection. A 'Manage Blocked Users' screen rendered from a one-shot fetch does not refresh when the user taps unblock. Observe the PagingData stream instead: getBlockedUsers().doOnNext { _: PagingData -> }.subscribe(). For a one-shot feed-filter snapshot, use getAllBlockedUsers instead.",
|
|
@@ -44,6 +47,7 @@
|
|
|
44
47
|
{
|
|
45
48
|
"id": "ios.blocked-users.live-collection",
|
|
46
49
|
"version": 1,
|
|
50
|
+
"symbol_anchored": { "platform": "ios", "types": ["AmityUserRepository"], "members": ["getBlockedUsers"], "basis": "names-only" },
|
|
47
51
|
"title": "iOS blocked-users list should use the reactive getBlockedUsers LiveCollection",
|
|
48
52
|
"severity": "warning",
|
|
49
53
|
"rationale": "The blocked-users list is a Live Collection. A 'Manage Blocked Users' screen rendered from a one-shot snapshot does not refresh when the user taps unblock. Observe the collection instead: getBlockedUsers().observe { ... }. For a one-shot feed-filter snapshot, use getAllBlockedUsers instead.",
|
|
@@ -57,6 +61,7 @@
|
|
|
57
61
|
{
|
|
58
62
|
"id": "flutter.blocked-users.live-collection",
|
|
59
63
|
"version": 1,
|
|
64
|
+
"symbol_anchored": { "platform": "flutter", "types": ["UserRepository"], "members": ["getBlockedUsers"], "basis": "names-only" },
|
|
60
65
|
"title": "Flutter blocked-users list should use the reactive getBlockedUsers LiveCollection",
|
|
61
66
|
"severity": "warning",
|
|
62
67
|
"rationale": "The blocked-users list is a Live Collection. A 'Manage Blocked Users' screen rendered from a one-shot fetch does not refresh when the user taps unblock. Wire a PagingController: PagingController(pageFuture: (token) => ...getBlockedUsers().getPagingData(token: token, limit: 20)).",
|
|
@@ -19,6 +19,18 @@ vise plan . --request "<user intent>"
|
|
|
19
19
|
vise init . --request "<user intent>"
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
For a **feed / community / experience** request, also run `vise creative . --request "<user intent>"` (the advisory Engagement Intelligence layer) before building — it surfaces engagement options (post types, polls, reactions, discovery/achievement loops) that `vise plan` only lists tersely under `optionalCapabilities` / `engagement`. Advisory, never a gate — full guidance under "Creative pre-planning" below.
|
|
23
|
+
|
|
24
|
+
**You own the goal→experience inference; EI is advisory data, not a decider.** `vise plan`'s automatic `socialWorkplan` expansion is a deterministic *convenience* that fires only on explicit experience **vocabulary** ("community feed", "direct messaging", "discussion forum", "creator platform") — it will **not** expand a goal phrased in natural product language ("let runners form local clubs and share their weekend routes", "buyers and sellers negotiate a sale privately", "users ask questions and get threaded answers"). So when the goal is experience-shaped but `vise plan` returns **no** `socialWorkplan`, do not conclude there is no fuller experience: run `vise creative . --request "<goal>"`, read its `candidateSolutions` as the advisory **menu** of experiences, and **infer which one fits the user's intent yourself** — mapping a fuzzy goal to an experience is your job (you are the inferential layer), not a keyword match (`creative`'s ranking is advisory; the *right* candidate is often in the set but not ranked first).
|
|
25
|
+
|
|
26
|
+
Before accepting a variant, **show the EI choice explicitly**. Do not compress this step into one sentence like "community-first is best." Present a compact decision block with:
|
|
27
|
+
- recommended experience and 2-3 concrete request/project signals that make it fit;
|
|
28
|
+
- 2-3 credible alternatives from `candidateSolutions`, each with `whenToChoose`, major tradeoff, and why it is not the recommendation;
|
|
29
|
+
- expected companion surfaces/objects the choice will bring into the blueprint;
|
|
30
|
+
- the no-fit option if none of the candidates genuinely matches.
|
|
31
|
+
|
|
32
|
+
Then accept the selected experience with `vise creative accept . --variant <id> --rationale "<why this fits the request>" --confidence high|medium|low`, drive the workplan, and confirm the companion set with the user at blueprint sign-off. Never read "no auto-match" as "no experience applies."
|
|
33
|
+
|
|
22
34
|
For broad social requests, `vise plan` may return `socialWorkplan.kind:
|
|
23
35
|
"social-multi-surface"`. In that case, do not force the whole request into one
|
|
24
36
|
sidecar. Drive the ordered workplan:
|
|
@@ -30,8 +42,58 @@ vise plan . --request "<user intent>" --answer feature_surface=<surface> ...
|
|
|
30
42
|
vise init . --request "<user intent>" --answer feature_surface=<surface> ...
|
|
31
43
|
```
|
|
32
44
|
|
|
45
|
+
If the user request explicitly names companion surfaces such as feed/post
|
|
46
|
+
creation, rich post rendering, comments/comment preview, reactions, profile or
|
|
47
|
+
follow graph, moderation/reporting, or chat, keep those surfaces in the blueprint
|
|
48
|
+
even when the accepted EI variant is community-first. "Community" is the
|
|
49
|
+
experience container, not permission to finish after only community discovery.
|
|
50
|
+
If `workplan status` says complete after a single community surface while the
|
|
51
|
+
original request still names feed/comments/reactions/profile/chat, treat that as
|
|
52
|
+
an alignment gap: run the focused `vise plan`/`vise init`/`vise check` loop for
|
|
53
|
+
the missing explicit surfaces or record the Vise gap honestly. Do not report the
|
|
54
|
+
broad integration complete from a community-only check when product code also
|
|
55
|
+
renders feed, composer, comments, reactions, profile/follow, moderation, or chat.
|
|
56
|
+
|
|
57
|
+
**Blueprint sign-off (the alignment gate — a SELECT, not just a confirm).** On a multi-surface request the FIRST `vise workplan next` returns `status: "needs-blueprint-confirmation"` instead of a surface, and writes `sp-vise/flow-blueprint.html`. Open that preview and review the whole plan with the user — the path, the experience, the ordered journey of surfaces, the scope, and the design. The gate presents the choices, it does not silently default them:
|
|
58
|
+
- **Solution path (when undecided).** If the request carries no UIKit/SDK signal, the response includes a `pathDecision` block (`required: true`) with the options (`uikit` / `sdk` / `hybrid`), and **the blueprint cannot be signed off until you pick one** — present them to the user, then re-run with `--answer solution_path=<sdk|uikit|hybrid>`. The choice persists in `sp-vise/flow.json` and is honored by every downstream surface plan automatically (you don't re-supply it). Do NOT default to SDK silently — ask.
|
|
59
|
+
- **Experience (recommended + alternatives).** The response carries an `experience` block: the recommended variant plus the other `candidateSolutions` from `vise creative`. This is the second chance to confirm the EI choice, not the first time the user should hear it. Confirm the recommendation with the user, or switch by re-running `vise creative accept . --variant <id> --rationale "<why this variant better fits the request>" --confidence high|medium|low` (which re-arms the sign-off with the new journey).
|
|
60
|
+
- **Confirm.** Once the path is chosen (when it was undecided) and the experience + journey are agreed, re-run with the digest it printed: `vise workplan next . --request "<user intent>" --answer solution_path=<choice> --answer blueprint_confirmation=<digest>`. That records the sign-off in `sp-vise/flow.json`, so later `workplan next`/`complete` calls pass without re-asking; you are re-prompted only if the path, journey, or scope materially changes.
|
|
61
|
+
|
|
62
|
+
**Surfaces cannot be started or recorded until the blueprint is signed off** — this is the "are we building the right thing?" gate that complements `vise check`'s "are we building it right?". Companion surfaces added by experience expansion (e.g. a community feed pulling in the community + comments surfaces) are tagged in the sequence with a `companion` tier — confirm or trim them at this step.
|
|
63
|
+
|
|
64
|
+
**Unattended blueprint gate.** If the user is not available, stop at `needs-blueprint-confirmation`. You may summarize/open `sp-vise/flow-blueprint.html`, but you must not choose `solution_path`, sign `blueprint_confirmation`, start `vise init`, or mark surfaces complete on the user's behalf. Return the `approvalChecklist`, path options, experience menu, surfaces, trims, and exact command to resume after approval.
|
|
65
|
+
|
|
66
|
+
**Pre-authorized dogfood/benchmark sign-off.** The unattended stop applies when the current user request has not already supplied the required approval. If a dogfood or benchmark prompt explicitly says the user has selected the solution path, surfaces/journey, runtime proof consent, and data-preparation policy, treat that prompt as the user's sign-off source. Run `vise workplan next`, read the printed `blueprintDigest`, then immediately re-run with the provided path choice and `--answer blueprint_confirmation=<digest>`; record in the transcript/notes that approval came from the benchmark prompt. If the gate asks for any material decision not covered by the prompt, or the prompt conflicts with the blueprint, stop and return the approval checklist instead of guessing.
|
|
67
|
+
|
|
68
|
+
**Trimming a companion you don't want to build.** If the user decides a companion surface is out of scope (e.g. the goal never asked for chat), record the omission with `vise workplan trim . --request "<user intent>" --surface <id> --reason "<why it's out of scope>"` (the surface-level analog of `// vise: scope-omit`; `--reason` is required). This is **not a bypass** — removing a surface changes the blueprint, so the sign-off gate re-fires and you must re-sign the *trimmed* blueprint (`workplan next ... --answer blueprint_confirmation=<new digest>`) before any surface proceeds; the re-confirmation names exactly what was dropped, so the user is approving the trim, not just a new digest. Trim only applies to a not-yet-built companion, and at least two surfaces must remain (to build a single surface, re-select that experience via `vise creative` rather than trimming a journey down to one). After the trim is signed, `workplan status` reaches `complete` once the remaining surfaces are built, and the omission is recorded in `sp-vise/flow.json`. Do NOT leave an unwanted companion permanently `next-surface` (stuck) or build a throwaway stub — trim it.
|
|
69
|
+
|
|
70
|
+
**Runtime readiness before dogfood.** Before feeding a workplan to a dogfood agent, review the `runtimeReadiness` block from `vise workplan next/status` (also rendered in `sp-vise/flow-blueprint.html`). It is advisory, but treat unknown identity or target data as blocking for runtime proof:
|
|
71
|
+
|
|
72
|
+
- Derive `userId` from the host app's existing auth/session/current profile whenever one exists.
|
|
73
|
+
- For dogfood-only projects, use an explicit local env/config userId and label it as dogfood data.
|
|
74
|
+
- Never invent or hardcode `userId`, `communityId`, `feedId`, `channelId`, reaction names, region, API keys, or tokens in source.
|
|
75
|
+
- Discover social targets naturally from host app state, route state, SDK list/search queries, user selection, or SDK create-flow return values. Do not ask users to paste `communityId`, `channelId`, `targetId`, or `postId` as setup unless the host app already owns that ID in normal route/app state.
|
|
76
|
+
- Confirm tenant data per selected surface before runtime smoke: discoverable communities, target feeds with visible posts, parent posts for comments, chat channels with membership/messages, profile users/follow relationships, or notification settings.
|
|
77
|
+
- If data is absent, ask whether to prepare it in Console or create safe seed data through SDK functions before judging the integration. If they decline, say that only static checks / resolved empty states can be claimed, not populated product proof.
|
|
78
|
+
- When safe SDK seed data is created, keep the returned community/feed/post/comment objects in runtime state or wait for a confirmed re-query/live-collection update before final handoff. A default path that still shows `0 posts`, `syncing`, `waiting on content`, create/prepare failure copy, or only disabled affordances is not populated runtime proof. If the exact product-context target cannot be created or populated because of tenant permissions or review mode, fall back to the nearest discovered readable target with visible posts/messages/users and map it back into the product context with honest copy. Do not silently swallow seed/query errors; record failures in handoff notes and show a product-native retry/error state.
|
|
79
|
+
- Do not dump arbitrary readable tenant data into product UI just because discovery returned it. Filter discovered communities/users/channels by product context, or create a product-native seed target. Names/descriptions that are raw IDs, numeric-only labels, short random strings such as `rand2`, lorem/test data, `sampleapp` fixtures, or unrelated generated business copy are not natural targets; hide or skip them and keep searching/seeding before claiming runtime proof. Prefer a product-owned namespace/allowlist such as app-created names, metadata tags, session/program identifiers, or cached IDs returned by the app's own create flow; if the SDK query returns mixed tenant data, render only product-safe matches and never let an unsafe target become the active selected context. Deduplicate discovered/seeded targets by stable SDK ID or normalized product label, and prioritize populated product-owned targets before empty clones; a long rail of repeated zero-post targets is not polished populated proof. Do not rewrite unsafe tenant labels into product copy to make them look acceptable — exclude the object. Apply this filtering to every visible rail/list/card and author fallback, not only the selected target.
|
|
80
|
+
- Runtime proof can consume browser/simulator/emulator/device resources and additional agent tokens. Ask the user before running it; if they decline, explain the remaining runtime risks instead of claiming the app was proven at runtime.
|
|
81
|
+
- Record the expected smoke level: use `populated` for data-backed surfaces where empty screens are not product proof, and `resolved` only where an empty state is legitimate.
|
|
82
|
+
- Never delete a runnable entry file such as `App.tsx`, `lib/main.dart`, or the app's root view as a standalone step while preparing a replacement. Edit it in place, or add/import a new child component/widget first, so interruptions and failed patches do not leave the project without an entry point.
|
|
83
|
+
- Record runtime readiness/proof status in evidence files, screenshots, or handoff notes — not as end-user product UI. Do not ship visible `CLIENT`, `SESSION`, `RUNTIME`, SDK, env-var, region, setup, readiness-debug labels, runtime/probe/status panels, or hidden no-op controls just to prove the app connected; the normal screen should express the product state naturally.
|
|
84
|
+
- Treat uncaught runtime errors as proof failures even if the screen looks populated. Browser `pageerror`/console exceptions such as `Cannot read properties of undefined`, React Native red boxes, Android fatal exceptions, Swift crashes, or SDK null/profile errors must be fixed or honestly recorded before handoff; screenshots are not enough when the runtime log says the social path is throwing.
|
|
85
|
+
- A repository query that stores SDK objects in state but never renders or passes that state into a visible widget/component is a hidden no-op, not runtime proof. The first social surface must consume SDK-returned communities/posts/comments/users in the actual product tree; returning the original screen after setup/login is still a failed integration.
|
|
86
|
+
- Native runtime proof must make the core social loop visibly reachable. Do not spend the whole first viewport on intro, hero, or community chrome while feed/composer/comment/reaction/report/profile surfaces are buried below an unverified scroll. For Flutter and React Native dogfood, keep at least one SDK-backed feed item and key actions visible in the initial route, or expose them through obvious product tabs/buttons that screenshots can capture; automated text/tap probes may not see Flutter/RN accessibility text.
|
|
87
|
+
- Runtime screenshots must be product-clean. Flutter/React Native debug artifacts such as yellow/black `RenderFlex` overflow banners, `BOTTOM OVERFLOWED BY ... PIXELS`, red error boxes, or clipped action rows are runtime failures to repair, not acceptable evidence. Use bounded card dimensions, `Flexible`/`Expanded`/`Wrap`, `maxLines`, ellipsis, and responsive spacing before handoff.
|
|
88
|
+
- If `vise check` fails `<platform>.live-collection.session-ready-requery` because `sp-vise/evidence/runtime-smoke.json` is missing or failed, do **not** attest it and do not call the surface complete. Declare `sp-vise/smoke.json` for the mounted collection surfaces, emit log-only markers from the real SDK query lifecycle (`VISE_SMOKE surface=<id> state=loading|empty|loaded count=N|error:<message>`), cold-launch the app into the social route, capture the device/browser log, run `vise smoke . --log <capture>`, and rerun `vise check . --ci`. The markers must never include secrets, PII, raw social IDs, or end-user setup/debug labels; they are evidence artifacts, not product UI. For Flutter/React Native dogfood, capture bounded Android logcat/simulator logs before `workplan complete` when the surface uses live collections.
|
|
89
|
+
|
|
33
90
|
Implement one surface at a time. Surface that focused plan's intake questions to
|
|
34
91
|
the user, including opt-in capability questions, then run the normal check loop.
|
|
92
|
+
Do not skip ahead to a later surface because it looks easier or because it is the
|
|
93
|
+
last item in the blueprint. After blueprint sign-off, `vise init` enforces the
|
|
94
|
+
next pending surface: initializing without `feature_surface`, or initializing
|
|
95
|
+
`profile`/`chat` before the pending `feed`/`community` surface, exits with a
|
|
96
|
+
workplan-order gate and prints the exact next command to run.
|
|
35
97
|
After `vise check`, `vise sync`, `vise validate`, and `vise run-sensors` pass,
|
|
36
98
|
record the surface:
|
|
37
99
|
|
|
@@ -53,6 +115,23 @@ vise search-docs "<SDK feature or API pattern>"
|
|
|
53
115
|
vise get-doc-page "<canonical-doc-path>"
|
|
54
116
|
```
|
|
55
117
|
|
|
118
|
+
**Implementation pacing matters.** Once the blueprint/intake gates are clear, make a visible product-source edit before broad research: route the host entry point to the new social surface, or create and import a product-native component for that surface. In bounded dogfood/benchmark runs, this is a hard ordering requirement: after the first `vise init` or focused surface command clears, edit product source before fetching a second docs page, grepping generated SDK/framework interfaces, resolving broad native package internals, or polishing UI. Do not create a shell-first component; the first social component should already import the relevant social.plus SDK package and contain setup/login plus at least one SDK data API call. If you need exact symbols, use package-root imports first, inspect the narrow package-root exports or one targeted type file, then let TypeScript/build errors guide the next lookup. Do not stop at static feature-preview cards that merely describe feed/comments/reactions/profile/chat; those are product copy, not integration. A run that only creates `sp-vise/` artifacts, installs dependencies, reads docs, edits dependency manifests/project files, or stops at a product-looking shell without SDK-backed source is not a completed Vise implementation.
|
|
119
|
+
|
|
120
|
+
The visible edit must also **use** the SDK data it fetches. If you add `_communities`, `posts`, `comments`, or similar SDK-backed state, render it or pass it into a rendered child before moving on. Unused SDK state, hidden controls, `void Repository` markers, iOS `let _ = AmityCommunityRepository.self` / type-only repository references, or a wrapper that still returns the original product screen are scanner-only integration and should be fixed before `vise check`.
|
|
121
|
+
|
|
122
|
+
Installing the SDK dependency is also not a completed integration. If `vise check`
|
|
123
|
+
reports `typescript.sdk.source-used`, `react-native.sdk.source-used`,
|
|
124
|
+
`flutter.sdk.source-used`, or `ios.sdk.source-used`, the app still has a
|
|
125
|
+
dependency-only/static social shell:
|
|
126
|
+
import/use the SDK or UIKit provider/manager in product source, initialize/login,
|
|
127
|
+
and query or create real social.plus data before spending more time on polish.
|
|
128
|
+
During dogfood, benchmark, or runtime-proof work, do **not** attest these
|
|
129
|
+
`*.sdk.source-used` findings. Treat them as hard stop conditions: fix product
|
|
130
|
+
source, rerun `vise check .`, and remove any stale source-used attestation before
|
|
131
|
+
claiming the integration is SDK-backed. An attestation for source-used in a
|
|
132
|
+
benchmark is evidence the workflow accepted a dependency-only/static shell too
|
|
133
|
+
early, not proof of a completed integration.
|
|
134
|
+
|
|
56
135
|
After editing:
|
|
57
136
|
|
|
58
137
|
```bash
|
|
@@ -75,9 +154,13 @@ no, do not pass `yes`; revise the design source and regenerate the preview, or
|
|
|
75
154
|
continue only after the user explicitly scopes the work without design
|
|
76
155
|
feed-forward.
|
|
77
156
|
|
|
78
|
-
**
|
|
157
|
+
**A `platformAvailability` notice means the feature is not in this platform's SDK — do not fabricate it.** When `vise plan` returns a `platformAvailability` entry (also surfaced as the first `[platform limitation]` line in `decisionsRequired`), the requested feature is not exposed by the social.plus SDK on the detected platform. Known Flutter cases: (1) **scheduled events / RSVP are not in the Flutter SDK** (iOS/Android/TypeScript only) — do not invent an `AmityEventRepository`; model events as community posts with metadata, use the server-side Event REST API, or build on a supported platform. (2) **Room-based livestreaming — broadcasting / go-live / room management (`RoomRepository.createRoom`/`getRoom`/`getRooms`) — is not in the Flutter SDK** (iOS/Android/TypeScript only); Flutter has only the deprecated legacy `StreamRepository.getStream` for *viewing* existing stream posts, so do not invent a `RoomRepository`/`createRoom` in Dart — build the broadcast experience on a supported platform, or render existing legacy stream posts read-only. Read the notice's `guidance` field and pick one of the stated alternatives. This is advisory and never gates `vise check`.
|
|
158
|
+
|
|
159
|
+
**Blocking intake questions must be surfaced to the user.** If `vise plan` returns `intake.status: "needs-clarification"` with blocking questions, stop and ask those questions before implementation. Current Vise also enforces this at `vise init`: unresolved blocking intake returns `status: "needs-clarification"` and the CLI exits non-zero. In unattended mode, return `decisionChecklist` / `intake.questions` to the user and stop; do not invent scope, route, identity, region, design, or capability answers. Only retrospective benchmark/harness setup may pass `--allow-unresolved-intake`, and that acknowledgement is recorded in `sp-vise/intake.json`.
|
|
79
160
|
|
|
80
|
-
**
|
|
161
|
+
**A `clarify` block means the request isn't buildable yet — resolve it, don't improvise.** When `vise plan` returns a `clarify` block (and `vise init` refuses with `status: "needs-clarification"` + a `clarify` block, exit 7), the request can't yet produce a buildable plan. Resolve each dimension in `clarify.resolve` with the user *before* building — do not silently pick a platform/scope or dead-end in questions: (1) **platform** — if no SDK platform is detected, confirm the target with the user and scaffold it (for web: `package.json` + `@amityco/ts-sdk`) so Vise can validate real code; (2) **experience** — if no buildable feature was named, run `vise creative .`, read the candidate experiences, show the EI choice block, and **infer the fit yourself** (`creative.routeTo`), then accept it with grounded rationale/confidence — Vise deliberately does not guess the experience from keywords. In unattended mode, follow `clarify.unattended`: stop and return the questions/checklist rather than selecting defaults. Re-run `vise plan`/`vise init` once resolved; the block clears when the request is buildable. `--allow-unresolved-intake` bypasses the init gate only for retrospective/harness setup.
|
|
162
|
+
|
|
163
|
+
**Baseline completeness is also a stop condition.** When `vise check .` exits with status `completeness-gap` (exit code 5), one or more baseline capabilities are neither implemented nor opted-out. For add-feed, the baseline capabilities are pagination and a comment preview (each rendered post must present a comment affordance — count + a top-comment snippet + a tap-through to the thread; read it off the post's `commentsCount`/`latestComments` or query `getComments`). A genuinely comment-less feed (broadcast/announcement, or a profile post-grid) opts out: `// vise: scope-omit comment-preview — <reason>`. For add-comments, the baseline capability is the comment composer/write affordance. For add-chat, the baseline capabilities are message send plus read/unread state. For add-follow/profile, the baseline capability is SDK-backed follower/following relationship data. For each missing item: either implement it, or place `// vise: scope-omit <id> — <reason>` in the relevant source file and re-run `vise check .` to confirm it exits 0. A missing baseline capability that is neither built nor opted-out is a silent drop — the check will not pass green until every baseline capability is resolved.
|
|
81
164
|
|
|
82
165
|
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.
|
|
83
166
|
|
|
@@ -134,6 +217,10 @@ When a secret is required:
|
|
|
134
217
|
|
|
135
218
|
If no safe ignored local env path or template path is clear, stop and tell the user which variable names are needed.
|
|
136
219
|
|
|
220
|
+
**Flutter** has no source-level `.env` convention: source the API key from a compile-time constant — `const String.fromEnvironment('AMITY_API_KEY')` — never a literal in source, and omit `defaultValue` so a missing key fails loudly instead of silently running on a placeholder. Prefer injecting real local values via an ignored file and `flutter run/build --dart-define-from-file=.dart_tool/<local-runtime-defines>.env`; do not put raw API key values in committed files or visible command arguments.
|
|
221
|
+
|
|
222
|
+
**iOS** likewise has no `.env` convention: source the API key from `ProcessInfo.processInfo.environment["AMITY_API_KEY"]` (or an `Info.plist` value backed by an `xcconfig` build setting), never a literal in source, and fail loudly (`guard let … else { fatalError(…) }`) on a missing value — the iOS analog of Flutter's `String.fromEnvironment`.
|
|
223
|
+
|
|
137
224
|
Do not write a real `.env` value for the user. If you create a local env file during implementation, it must contain placeholders only and the project must already ignore it. `vise check` flags committed env secrets and missing TypeScript env hygiene.
|
|
138
225
|
|
|
139
226
|
## Logging Discipline
|
|
@@ -147,6 +234,16 @@ Do not pass secret-shaped values (`apiKey`, `apiSecret`, `accessToken`, `refresh
|
|
|
147
234
|
|
|
148
235
|
`vise check` enforces `*.logging.no-secret-in-log` per platform. If you need to log around a value for debugging, log a redacted form (`apiKey ? "<set>" : "<missing>"`), not the value itself. Remove debug log lines before declaring done.
|
|
149
236
|
|
|
237
|
+
## Client Initialization Shape
|
|
238
|
+
|
|
239
|
+
For TypeScript and React Native, `Client.createClient` takes **positional** arguments — `Client.createClient(apiKey, apiRegion?)` — **not** an options object. The object form `Client.createClient({ apiKey, region })` appears in some examples, but it does not match the SDK's TypeScript declaration: it fails `tsc`, and at runtime the unset region surfaces as a `Failed to construct 'URL'` error on `login`. Pass the region **positionally** as the second argument — `Client.createClient(apiKey, API_REGIONS.SG)` (or a `'sg' | 'us' | 'eu'` value) — never as a `{ region }` field. React Native uses the same positional shape via `@amityco/ts-sdk-react-native` (the React Native package id — `@amityco/react-native-sdk` does not exist on npm). React Native apps must also declare the SDK's native-module peers at the app level, including `@react-native-async-storage/async-storage` v1.x and `@react-native-community/netinfo`, then rebuild the native app so autolinking registers `AsyncStorage` and `RNCNetInfo`.
|
|
240
|
+
|
|
241
|
+
On **Flutter**, the shape is different: `AmityCoreClient.setup` takes an options object built with `AmityCoreClientOption.create(apiKey: ..., endpoint: ...)`, and the region is the **`AmityEndpoint` enum** (e.g. `AmityEndpoint.SG` / `AmityEndpoint.US` / `AmityEndpoint.EU`) — not a raw region string. Call `WidgetsFlutterBinding.ensureInitialized()` before `await AmityCoreClient.setup(...)` in an `async main()`. For runtime smoke/proof apps that query SDK data immediately after setup/login, use the SDK's synchronous initialization path (`sycInitialization: true`) or otherwise wait for SDK initialization to complete before login/query; a build can pass while runtime fails with `MessageDbAdapter` not ready.
|
|
242
|
+
|
|
243
|
+
For **Flutter `amity_sdk` 7.x**, add a direct Dio compatibility guard before Android build proof: `dio: 5.9.2` (or a bounded `<5.10.0` constraint) in `pubspec.yaml`, then run `flutter pub get` and confirm `pubspec.lock` resolves Dio below 5.10.0. `amity_sdk` 7.x allows `dio ^5.4.0`; a fresh resolver can choose Dio 5.10.0, whose `DioExceptionType.transformTimeout` case makes the SDK's switch non-exhaustive and fails `assembleDebug` even when analyze/test pass.
|
|
244
|
+
|
|
245
|
+
On **iOS (Swift)**, the client is `try AmityClient(apiKey: ..., region: ...)` — a **throwing** convenience initializer; the region is the **`AmityRegion` enum** (`.SG` / `.US` / `.EU`), not a string.
|
|
246
|
+
|
|
150
247
|
## Session Renewal
|
|
151
248
|
|
|
152
249
|
When you call SDK `login`, also implement the session-renewal handler in the same change. Access tokens expire in production; without a renewal handler the SDK cannot refresh and customers see silent auth failures.
|
|
@@ -154,11 +251,11 @@ When you call SDK `login`, also implement the session-renewal handler in the sam
|
|
|
154
251
|
Per platform:
|
|
155
252
|
|
|
156
253
|
- TypeScript / React Native: pass `sessionHandler` to `Client.login`, implement `sessionWillRenewAccessToken(renewal)` and call `renewal.renew()`.
|
|
157
|
-
- Android Kotlin: pass `
|
|
158
|
-
- Flutter Dart:
|
|
159
|
-
- iOS Swift: implement `
|
|
254
|
+
- Android Kotlin: pass a `SessionHandler` to `AmityCoreClient.login`, override `sessionWillRenewAccessToken` and call `renewal.renew()`. (The type is `SessionHandler`, not `AmitySessionHandler` — per vise's Android surface catalog.)
|
|
255
|
+
- Flutter Dart: pass a `void Function(AccessTokenRenewal)` callback as the `sessionHandler:` named argument to `AmityCoreClient.login(userId, sessionHandler: ...)` and call `renewal.renew()` inside it. The parameter type is `AccessTokenRenewal`, and the handler is a plain callback/method — there is **no** `AmitySessionHandler` class and **no** `AmityAccessTokenRenewal` type on any platform (Android and iOS use the bare `SessionHandler` protocol with `AccessTokenRenewal`).
|
|
256
|
+
- iOS Swift: conform a type to the `SessionHandler` protocol (NOT `AmitySessionHandler`, which does not exist), implement `sessionWillRenewAccessToken(renewal:)` calling `renewal.renew()`, and pass it to `client.login(...)` — e.g. `final class AppSessionHandler: SessionHandler { func sessionWillRenewAccessToken(renewal: any AccessTokenRenewal) { renewal.renew() } }`.
|
|
160
257
|
|
|
161
|
-
**Do not fire SDK collection queries in the same synchronous tick as `login()
|
|
258
|
+
**Do not fire SDK collection queries in the same synchronous tick as `login()` — and do not gate them on a *one-shot* readiness check that never re-fires.** The screen that consumes live data usually mounts *before* login resolves. A readiness check that runs once in `onAppear` / `useEffect([])` / `initState` / `onViewCreated` and returns early when the session isn't ready yet leaves the surface **permanently empty** — nothing re-runs the query when the session later becomes active. Drive the query off a **reactive** session/auth-state signal — observe the session state *and* seed it synchronously from the current value (an `@Published`/`useState(client.sessionState)`/`StateFlow` read), so an already-established session isn't missed and a later transition still fires the query. For TypeScript / React Native specifically, do not rely on `Client.onSessionStateChange` alone: after `Client.createClient(...)`, keep a stateful `clientReady` flag, call `Client.login(...)` from an effect that depends on `clientReady` + `userId`, set an explicit `authReady`/`loggedIn` state when the login promise resolves, and run SDK list/create queries from that `authReady` state (or from a session value normalized through a helper such as `String(state).toLowerCase() === "established"`). Add bounded login retry/error handling so a cold launch that stays `notLoggedIn` for a few seconds shows a product-native retry state instead of a permanent spinner. For Flutter specifically, do not rely on `AmityCoreClient.observeSessionState().listen(...)` to emit the initial `NotLoggedIn` state; after subscribing, also kick the idempotent login/session branch immediately from `initState` (for example with `Future.microtask(_login)` or an equivalent current-state branch), then let later observer emissions re-run post-login queries. The query must **(re-)run when the session becomes active**, whether that is before or after the view mounted. On a warm local machine login resolves first by chance and the one-shot check passes; on a cold start it fires too early, or the initial observer emission is missed, and the surface stays empty/signing-in. Render the not-ready and error states as **retryable** (a visible retry affordance), never a silent dead end. *(Observed across web and native dogfoods: the app compiles, `vise check` is green, and the screen is stuck on empty/“logging in” because the query ran once, too early, and never again.)*
|
|
162
259
|
|
|
163
260
|
### Session Handler Retention
|
|
164
261
|
|
|
@@ -303,6 +400,10 @@ For a **new (greenfield) install**, resolve the **current latest** SDK at instal
|
|
|
303
400
|
|
|
304
401
|
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.
|
|
305
402
|
|
|
403
|
+
On **Flutter**, the latest `amity_sdk` may require a newer Dart SDK than your installed toolchain. If `flutter pub get` reports a Dart SDK constraint conflict, pin the latest `amity_sdk` that resolves under your Dart SDK rather than forcing a toolchain upgrade — that is still an explicit pin, not a floating version.
|
|
404
|
+
|
|
405
|
+
On **iOS**, the SwiftPM package is `github.com/AmityCo/Amity-Social-Cloud-SDK-iOS-SwiftPM` (product `AmitySDK`), distributed as a binary `xcframework`. Pin an **exact** version — `exactVersion` in an xcodegen `project.yml`, or `.exact(...)` / `from:` in `Package.swift` — never a `branch:` ref.
|
|
406
|
+
|
|
306
407
|
## Resolving `vise check` Findings
|
|
307
408
|
|
|
308
409
|
`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`**.
|
|
@@ -328,15 +429,15 @@ Do not report the integration complete while any baseline capability is `missing
|
|
|
328
429
|
|
|
329
430
|
Optional feed choices are not baseline compliance. If the user explicitly chooses one during planning, carry that decision into init, for example `vise init . --request "Add a social feed" --answer feed_optional_capabilities=post-poll-creation`. Once recorded, `vise check .` runs the selected source sensors and exits `selected-capability-failures` (exit code 6) until the selected capability is implemented.
|
|
330
431
|
|
|
331
|
-
**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
|
|
432
|
+
**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 literal or env-provided Social+ object IDs such as `communityId`, `postId`, `channelId`, `targetId`, or `feedId`. The app should discover or create targets at runtime, then pass the returned SDK object or ID through normal app state:
|
|
332
433
|
|
|
333
|
-
- Flutter:
|
|
334
|
-
- TypeScript / React / Next.js:
|
|
335
|
-
- React Native:
|
|
336
|
-
- Android: `BuildConfig.AMITY_COMMUNITY_ID`
|
|
337
|
-
- iOS: `
|
|
434
|
+
- Flutter: resolve the target in a state object/BLoC/provider from an SDK query or create flow, then pass the selected object/ID into the widget tree.
|
|
435
|
+
- TypeScript / React / Next.js: resolve the target with SDK hooks/repositories, route params that already belong to the host app, or user selection state; do not add `NEXT_PUBLIC_AMITY_COMMUNITY_ID`, `VITE_AMITY_POST_ID`, or similar.
|
|
436
|
+
- React Native: resolve the target with SDK-backed state, navigation params owned by the app, or user selection state; do not add object-ID env vars through `react-native-config` or build-time replacement.
|
|
437
|
+
- Android: resolve the target in the ViewModel from SDK queries, app route state, or a create flow; do not add `BuildConfig.AMITY_COMMUNITY_ID` or similar object-ID fields.
|
|
438
|
+
- iOS: resolve the target in an `ObservableObject`/coordinator from SDK queries, app route state, or a create flow; do not read object IDs from `ProcessInfo` or `xcconfig`.
|
|
338
439
|
|
|
339
|
-
|
|
440
|
+
It is fine for a smoke test to record the resolved object ID in evidence logs or screenshots so reviewers can trace what happened. Do not make that ID an input contract for the user.
|
|
340
441
|
|
|
341
442
|
**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.
|
|
342
443
|
|
|
@@ -419,7 +520,7 @@ vise search-docs "community create join leave members invitation"
|
|
|
419
520
|
vise plan . --request "<community request>"
|
|
420
521
|
```
|
|
421
522
|
|
|
422
|
-
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
|
|
523
|
+
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 natural community source (discovery query, create-flow return value, route/app state, or user selection; 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>`.
|
|
423
524
|
|
|
424
525
|
For the social graph (follow/followers):
|
|
425
526
|
|
|
@@ -444,13 +545,52 @@ Observe the notification tray as a Live Object/Collection, mark items/tray seen
|
|
|
444
545
|
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:
|
|
445
546
|
|
|
446
547
|
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`.
|
|
447
|
-
2. **Host app's own design system.**
|
|
548
|
+
2. **Host app's own *pre-existing* design system.** When the project ALREADY has a real design system that predates your work, run `vise design extract --from-project .` to derive the contract from it — 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.
|
|
448
549
|
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.
|
|
449
550
|
|
|
551
|
+
> **Guardrail — never grade your own new CSS against itself.** `--from-project` is only legitimate for a design system that *already existed*. Do **not** write the feature's styling yourself and then run `--from-project` on it — that produces a **self-referential** contract (Vise now tags it `source.self_referential: true` and caps its `strength` below `strong`), and `vise design check` will report high token coverage that is *circular*, not a sign the design is good. If there is no real external design target, take option 3 and **record "no design-conformance claim" in your summary** rather than manufacturing a contract from your own output. A real design target is a customer prototype, their actual theme/token file, or a screenshot — get one (option 1) before claiming conformance.
|
|
552
|
+
|
|
450
553
|
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).
|
|
451
554
|
|
|
452
555
|
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.
|
|
453
556
|
|
|
557
|
+
**Brand profile (beyond tokens — optional, advisory).** A token contract captures colours/spacing/radius, but it misses the brand *identity* that makes a UI feel on-brand: personality, voice, type treatment, density, how the accent is rationed, what to avoid. When a design contract is accepted, `vise plan` carries a `brandGrounding` field. If it reads `status: "absent"`, you may author `sp-vise/design-profile.json` from the host's **real screens** (screenshots / live UI) to capture what a token scrape can't — and then `vise plan` surfaces it as `status: "active"` and honours it. This is purely advisory grounding; it never gates `vise check` and never changes the contract digest. Schema — **every field below except `capturedFrom` is required** (and within `typography`, the individual sub-keys are optional). A profile that omits any required field is **silently discarded** (vise reads it as `status: "absent"` with no error), so fill all required fields with your best judgment from the real screens rather than dropping one. The six string fields `essence`/`voice`/`density`/`elevation`/`motion`/`imagery` must be non-empty:
|
|
558
|
+
|
|
559
|
+
```json
|
|
560
|
+
{
|
|
561
|
+
"schemaVersion": "0.1.0",
|
|
562
|
+
"essence": "one sentence of the brand identity",
|
|
563
|
+
"personality": ["4-7 adjectives"],
|
|
564
|
+
"voice": "the tone of UI microcopy this brand uses",
|
|
565
|
+
"palette": [{ "role": "bg|surface|text|textDim|accent|accentText|line", "hex": "#…", "note": "e.g. accent is rationed — never on avatars" }],
|
|
566
|
+
"typography": { "headingFamily": "…", "bodyFamily": "…", "headingStyle": "…", "scaleNote": "…", "letterSpacing": "…" },
|
|
567
|
+
"density": "dense|comfortable|airy + rhythm note",
|
|
568
|
+
"elevation": "flat|soft-shadow|strong",
|
|
569
|
+
"motion": "implied motion energy",
|
|
570
|
+
"imagery": "gradient-heavy | photographic | minimal-illustrative | iconographic",
|
|
571
|
+
"do": ["4-6 concrete on-brand directives"],
|
|
572
|
+
"avoid": ["3-5 off-brand things"],
|
|
573
|
+
"capturedFrom": "screen-or-source the profile was read from"
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
When `brandGrounding.status` is `active`, `vise plan` carries these rules verbatim — `palette` (roles + notes, e.g. the accent-rationing rule), `imagery`, `density`, `typography`, `motion`, `do`/`avoid` — so a plan-only agent has them without opening the file. Then build each surface honouring the profile's palette *roles*, type treatment, density, shape, and voice — not just its hex values. A malformed or incomplete profile is silently ignored, so author it complete (every required field present, the six listed strings non-empty) — `motion`/`elevation`/`imagery`/`density` are judgment calls you can always make from real screens (pick the closest enum value); don't drop them.
|
|
578
|
+
|
|
579
|
+
**Design review loop (custom UI only — advisory).** When you're building **custom UI** (raw-SDK, you write the layout) *and* a brand profile exists, `vise plan` carries a `designReviewLoop` block — a brand-parameterized critique checklist + iterate protocol. Use it as a generation-time self-check on the surface you're building *now*: **build → render it (headless browser / your preview tooling) → score the render against the block → revise & re-render, up to ~3 rounds.** The checks are concrete, not vibes: this brand's named `brandAvoids`, the `paletteDiscipline` rules (e.g. accent rationing), `accessibility` (vise-**computed** WCAG ratios on the palette pairs — don't ship a round with a failing pair), and `statesRequired` (empty/loading/error). **Don't eyeball contrast** — a faint colour looks fine on a bright screen and still fails AA. For any text/icon colour you use that *isn't* one of those palette pairs (a one-off "subtle" grey, say), compute it with **`vise design contrast <foreground> <background>`** and keep it ≥4.5:1 (3:1 for large/bold text). It is **advisory** — vise never edits your code and this never gates `vise check`; your own design choices (and edits to `design-profile.json`) are authority, and it is *not* an ongoing audit of work you've already tuned. On the **UIKit path** the block's `status` is `uikit` (the components are themed prebuilt — there's no custom render to critique): run `vise design check` to verify your theme JSON matches the brand tokens instead.
|
|
580
|
+
|
|
581
|
+
**`vise check` green ≠ good-looking UI.** Compliance gates SDK *correctness* and feature *completeness*; visual quality is advisory and never gates. After building UI, read `vise design check`: a high `colorLiterals.off_contract_ratio` (with a `palette_review` note) means the palette is mostly ad-hoc hardcoded colors rather than token-driven — a strong signal the UI is incoherent even though `check` is green. Treat that as work remaining, and avoid trendy-but-noisy defaults (gradients, glows, glassmorphism, emoji icons) unless the design target calls for them.
|
|
582
|
+
|
|
583
|
+
## Creative pre-planning (Engagement Intelligence — optional)
|
|
584
|
+
|
|
585
|
+
Before `vise plan`, you *may* run `vise creative . --request "<intent>"` — the advisory Engagement Intelligence layer. It returns `candidateSolutions` (distinct ways to meet the goal), `selectionGuidance`, and `questions`, and accepts a design prototype and a requirements doc as inputs. It is advisory: review context, never a gate, and it does not change outcome classification or `vise check`. When you use it, present the EI choice block before accepting a variant; the user should see the recommended experience, real alternatives, tradeoffs, expected blueprint surfaces, and no-fit option.
|
|
586
|
+
|
|
587
|
+
Run it when it actually helps — not on every request:
|
|
588
|
+
|
|
589
|
+
- **The intent is fuzzy or experience-shaped** ("make the feed engaging", "what should we build", "drive retention") — use it to surface options and tradeoffs before committing.
|
|
590
|
+
- **A real design prototype or requirements doc exists** — pass it in: `vise creative . --request "<intent>" --prototype <prototype.html> --requirements <reqs.md>`. This is the front door for a real design target: the prototype you feed `creative` is the same one to run through `vise design extract <prototype>` (option 1 above), so the design intent threads from creative → contract → build. (Skipping creative is fine for a concrete, well-specified request.)
|
|
591
|
+
|
|
592
|
+
`vise creative` does NOT make the UI look good by itself — it helps you choose *what* and *with which engagement options*; visual quality still comes from a real design target plus your own craft (see the design section above).
|
|
593
|
+
|
|
454
594
|
## Monorepos
|
|
455
595
|
|
|
456
596
|
If `vise inspect .` reports multiple surfaces, pick the intended app and pass `--surface <path>` consistently to `plan`/`validate`/`run-sensors`. Details: `reference/operations.md`.
|
|
@@ -465,3 +605,11 @@ Report:
|
|
|
465
605
|
- validation findings
|
|
466
606
|
- sensor results or why sensors were skipped
|
|
467
607
|
- unresolved user inputs or stop conditions
|
|
608
|
+
|
|
609
|
+
Do not collapse the handoff to "green" if review debt remains. Always report:
|
|
610
|
+
|
|
611
|
+
- `vise validate` status and every warning/error finding that remains, even when `vise check` is green.
|
|
612
|
+
- Unresolved non-blocking intake questions from `sp-vise/intake.json` (for example optional capabilities, design-source confirmation, primary-action token fallback), with the decision still needed.
|
|
613
|
+
- Any acknowledged unresolved blocking intake from `sp-vise/intake.json` when `--allow-unresolved-intake` was used.
|
|
614
|
+
|
|
615
|
+
`vise experience-report .` mirrors these under `report.openReviewItems`; use it as the final-report source of truth when present.
|