Accept stringified Ranker bridge envelopes
This commit is contained in:
@@ -49,7 +49,7 @@ Ranker's continuation job is narrow:
|
|||||||
|
|
||||||
Candidate items may include optional 1–10 `rankerHints` (`value`, `effort`, `confidence`, `urgency`, `revenue`, `novelty`, `risk`). Ranker blends those explicit Scattermind hints with text heuristics; `effort` is inverted into feasibility. Hints improve the defended order, but `recommendedLane: "defer"` or `"park"` remains a safety rail so dashboard-swamp items do not get promoted by flashy wording. For clean bridge handoff, Scattermind should send `sourceSection` and `evidenceNeeded` on each active next move. Scattermind can also send `targetAudience`, `constraints`, `assumptions`, and `nonGoals` / `avoid` at the top level, in `featureSet`, inside `snapshot.context` or `conceptMap.context`, or as a structured top-level `context` object with `summary`, `targetAudience`, `constraints`, `nonGoals` / `avoid`, and `assumptions`; Ranker merges these sources rather than letting a shallow wrapper context shadow deeper artifact guardrails. Ranker also accepts structured `decisionContext` / `decision_context`, `rankerContext` / `ranker_context`, `handoffContext` / `handoff_context`, and `bridgeContext` / `bridge_context` objects at the top level, inside bridge envelopes, feature sets, artifacts, Snapshots, or Concept Maps, so Scattermind can keep context in a machine-named contract field without losing guardrails. Lens-only Concept Maps may additionally send audience / constraints / assumptions / risk lens content, and Ranker will split sentence-style lens notes into readable decision context instead of leaking `[object Object]`. If Scattermind only has a flat context string or schema-light structured context summaries (`context.summary`, `snapshot.context.summary`, `conceptMap.context.summary`, etc.), Ranker extracts simple guardrails such as `Solo builder`, `Manual proof`, `Avoid ...`, `No ...`, `Non-goal: ...`, `Not yet ...`, and `Do not ...` into `input.decisionContext` / `handoff.decisionContext` so early bridge exports still protect against dashboard/auth/billing drift. Ranker returns that decision context in `input.decisionContext` and `handoff.decisionContext`, and penalizes candidates that conflict with source non-goals (for example saved workspaces/auth/billing before the continuation proof). If Scattermind sends duplicate candidate IDs, Ranker keeps the first ID, suffixes later duplicates (`preview-2`), and reports the normalization in `handoff.warnings` / `handoff.itemTrace` so downstream build-order references remain addressable. Ranker also accepts Scattermind's paid Concept Map object directly when it arrives with top-level `reference_code`, `working_name`, `ideaText`, and string-valued `lenses.channel` / `lenses.risk` fields; the reference code becomes source provenance, the working name becomes the source title, and labelled Build Order text is turned into rank-ready candidates without requiring Scattermind to wrap it first.
|
Candidate items may include optional 1–10 `rankerHints` (`value`, `effort`, `confidence`, `urgency`, `revenue`, `novelty`, `risk`). Ranker blends those explicit Scattermind hints with text heuristics; `effort` is inverted into feasibility. Hints improve the defended order, but `recommendedLane: "defer"` or `"park"` remains a safety rail so dashboard-swamp items do not get promoted by flashy wording. For clean bridge handoff, Scattermind should send `sourceSection` and `evidenceNeeded` on each active next move. Scattermind can also send `targetAudience`, `constraints`, `assumptions`, and `nonGoals` / `avoid` at the top level, in `featureSet`, inside `snapshot.context` or `conceptMap.context`, or as a structured top-level `context` object with `summary`, `targetAudience`, `constraints`, `nonGoals` / `avoid`, and `assumptions`; Ranker merges these sources rather than letting a shallow wrapper context shadow deeper artifact guardrails. Ranker also accepts structured `decisionContext` / `decision_context`, `rankerContext` / `ranker_context`, `handoffContext` / `handoff_context`, and `bridgeContext` / `bridge_context` objects at the top level, inside bridge envelopes, feature sets, artifacts, Snapshots, or Concept Maps, so Scattermind can keep context in a machine-named contract field without losing guardrails. Lens-only Concept Maps may additionally send audience / constraints / assumptions / risk lens content, and Ranker will split sentence-style lens notes into readable decision context instead of leaking `[object Object]`. If Scattermind only has a flat context string or schema-light structured context summaries (`context.summary`, `snapshot.context.summary`, `conceptMap.context.summary`, etc.), Ranker extracts simple guardrails such as `Solo builder`, `Manual proof`, `Avoid ...`, `No ...`, `Non-goal: ...`, `Not yet ...`, and `Do not ...` into `input.decisionContext` / `handoff.decisionContext` so early bridge exports still protect against dashboard/auth/billing drift. Ranker returns that decision context in `input.decisionContext` and `handoff.decisionContext`, and penalizes candidates that conflict with source non-goals (for example saved workspaces/auth/billing before the continuation proof). If Scattermind sends duplicate candidate IDs, Ranker keeps the first ID, suffixes later duplicates (`preview-2`), and reports the normalization in `handoff.warnings` / `handoff.itemTrace` so downstream build-order references remain addressable. Ranker also accepts Scattermind's paid Concept Map object directly when it arrives with top-level `reference_code`, `working_name`, `ideaText`, and string-valued `lenses.channel` / `lenses.risk` fields; the reference code becomes source provenance, the working name becomes the source title, and labelled Build Order text is turned into rank-ready candidates without requiring Scattermind to wrap it first.
|
||||||
|
|
||||||
Candidate trace note: candidate-level `sourceItemId` / `traceId`, `sourceTitle` / `lensTitle`, and `sourceExcerpt` / `sourceQuote` are preserved in ranked items, `buildOrderDetails`, and `handoff.itemTrace`. Lens-only Build Order text is also split into deterministic `concept-map.lenses.channel#N` source IDs with the original labelled sentence carried as `sourceQuote`, so pasted paid Concept Maps remain traceable even without explicit candidate objects. String items in laned Build Order arrays now also receive deterministic section-local source IDs such as `concept-map.buildOrder.validateNext#1` and carry the original string as `sourceQuote`, so simple Scattermind exports stay addressable downstream instead of becoming anonymous `feature-1` rows. Ranker also accepts the current Scattermind storage-row shape with `referenceCode`, `ideaText`, `context`, and string-valued `fullReadingJson` / `full_reading_json`; it expands the saved paid Concept Map before ranking so operators do not have to hand-copy lenses out of Appwrite rows. If the row only has free Snapshot data (`glimpseJson` / `glimpse_json` / `snapshotJson`), Ranker expands that Snapshot into a minimal continuation order: one manual proof plus the first evidence question, with the Snapshot reference code/title preserved for provenance. The public paste form mirrors this: a prose-wrapped/fenced Appwrite row paste stays intact for the API instead of unwrapping the stringified reading into nonsense fields. The decision `brief.quickGlance.sourceTrace` now repeats the winning item's source section/id/title/quote, and both `brief.source.originalPromptExcerpt` / `handoff.source.originalPromptExcerpt` or, when the original prompt is unavailable, `sourceSummaryExcerpt` carry the source context so a downstream Scattermind handoff can show why the build order exists without digging through `input.provenance`. Scattermind should use these when a next move came from a specific Concept Map lens sentence, so Ranker can defend not just what wins but where the judgement came from.
|
Candidate trace note: candidate-level `sourceItemId` / `traceId`, `sourceTitle` / `lensTitle`, and `sourceExcerpt` / `sourceQuote` are preserved in ranked items, `buildOrderDetails`, and `handoff.itemTrace`. Lens-only Build Order text is also split into deterministic `concept-map.lenses.channel#N` source IDs with the original labelled sentence carried as `sourceQuote`, so pasted paid Concept Maps remain traceable even without explicit candidate objects. String items in laned Build Order arrays now also receive deterministic section-local source IDs such as `concept-map.buildOrder.validateNext#1` and carry the original string as `sourceQuote`, so simple Scattermind exports stay addressable downstream instead of becoming anonymous `feature-1` rows. Ranker also accepts the current Scattermind storage-row shape with `referenceCode`, `ideaText`, `context`, and string-valued `fullReadingJson` / `full_reading_json`; it expands the saved paid Concept Map before ranking so operators do not have to hand-copy lenses out of Appwrite rows. Stringified bridge envelopes in fields such as `rankerInput`, `rankerBridge`, `rankReady`, `bridgePayload`, or `continuationPlan` are expanded the same way, so Appwrite/string-copy handoffs do not have to be manually unwrapped before ranking. If the row only has free Snapshot data (`glimpseJson` / `glimpse_json` / `snapshotJson`), Ranker expands that Snapshot into a minimal continuation order: one manual proof plus the first evidence question, with the Snapshot reference code/title preserved for provenance. The public paste form mirrors this: a prose-wrapped/fenced Appwrite row paste stays intact for the API instead of unwrapping the stringified reading into nonsense fields. The decision `brief.quickGlance.sourceTrace` now repeats the winning item's source section/id/title/quote, and both `brief.source.originalPromptExcerpt` / `handoff.source.originalPromptExcerpt` or, when the original prompt is unavailable, `sourceSummaryExcerpt` carry the source context so a downstream Scattermind handoff can show why the build order exists without digging through `input.provenance`. Scattermind should use these when a next move came from a specific Concept Map lens sentence, so Ranker can defend not just what wins but where the judgement came from.
|
||||||
|
|
||||||
Soft Scattermind labels are accepted at the bridge boundary so Scattermind does not need to use harsh verdict copy in its own product surface. Lens text can say `Continue first`, `Make tangible`, `Try next`, `Evidence next`, `Hold for later`, or `Set aside`, with either colon or reader-friendly dash separators (`Continue first: …`, `Continue first — …`, `Evidence next - …`). Build Order objects and direct bridge/envelope sections can use matching camel/snake-case keys such as `continueFirst`, `evidenceNext`, `holdForLater`, and `setAside`. Ranker maps those to `doFirst / validateNext / defer / park` while preserving the softer original label in `sourceQuote` or candidate source trace. Ranker also accepts softer continuation envelopes named `rankerBridge`, `continuation`, or `continuationPlan`, candidate arrays named `possibleNextMoves`, `suggestedNextMoves`, `recommendations`, or `opportunities`, laned `buildOrderPreview` / `build_order_preview` objects, and evidence-question fallback arrays (`evidenceQuestions` / `evidence_questions`, `decisionQuestions`, `questionsToAnswer`, `followupQuestions`). If a paid Concept Map has no labelled Build Order/action threads but does include `closing_note` / `closingNote` plus decision questions, Ranker treats the closing note as the active 48-hour Do first move and keeps the questions in Validate next. This lets Scattermind pass reader-friendly Concept Map copy without renaming everything into software-feature language.
|
Soft Scattermind labels are accepted at the bridge boundary so Scattermind does not need to use harsh verdict copy in its own product surface. Lens text can say `Continue first`, `Make tangible`, `Try next`, `Evidence next`, `Hold for later`, or `Set aside`, with either colon or reader-friendly dash separators (`Continue first: …`, `Continue first — …`, `Evidence next - …`). Build Order objects and direct bridge/envelope sections can use matching camel/snake-case keys such as `continueFirst`, `evidenceNext`, `holdForLater`, and `setAside`. Ranker maps those to `doFirst / validateNext / defer / park` while preserving the softer original label in `sourceQuote` or candidate source trace. Ranker also accepts softer continuation envelopes named `rankerBridge`, `continuation`, or `continuationPlan`, candidate arrays named `possibleNextMoves`, `suggestedNextMoves`, `recommendations`, or `opportunities`, laned `buildOrderPreview` / `build_order_preview` objects, and evidence-question fallback arrays (`evidenceQuestions` / `evidence_questions`, `decisionQuestions`, `questionsToAnswer`, `followupQuestions`). If a paid Concept Map has no labelled Build Order/action threads but does include `closing_note` / `closingNote` plus decision questions, Ranker treats the closing note as the active 48-hour Do first move and keeps the questions in Validate next. This lets Scattermind pass reader-friendly Concept Map copy without renaming everything into software-feature language.
|
||||||
|
|
||||||
|
|||||||
@@ -1829,6 +1829,44 @@ try {
|
|||||||
assert.equal(buildOrderPreview.handoff.readiness.status, 'ready');
|
assert.equal(buildOrderPreview.handoff.readiness.status, 'ready');
|
||||||
assert.deepEqual(buildOrderPreview.handoff.warnings, []);
|
assert.deepEqual(buildOrderPreview.handoff.warnings, []);
|
||||||
|
|
||||||
|
const stringifiedRankerInputResponse = await fetch(`${base}/api/rank-feedback`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
sourceName: 'Scattermind',
|
||||||
|
rankerInput: JSON.stringify({
|
||||||
|
schema: 'scattermind-ranker-bridge-v1',
|
||||||
|
sourceName: 'Scattermind',
|
||||||
|
reference_code: 'SM-STRING-RANKER-INPUT-1',
|
||||||
|
working_name: 'Stringified ranker input handoff',
|
||||||
|
original_prompt: 'A tired user pasted an Appwrite/stringified rankerInput bridge field instead of a clean object.',
|
||||||
|
decision_context: {
|
||||||
|
target_audience: 'Tired non-AI-native solo builder',
|
||||||
|
non_goals: ['Avoid saved workspace dashboards before source-traced handoff proof'],
|
||||||
|
constraints: ['Manual proof before product surface'],
|
||||||
|
},
|
||||||
|
build_order_preview: {
|
||||||
|
continue_first: [{ id: 'stringified-active-slice', action: 'Stringified active slice', evidence_needed: 'Can a stringified rankerInput field still produce a traceable Do first?', source_item_id: 'stringified-preview-1', source_title: 'Stringified Build Order Preview', ranker_hints: { value: 9, effort: 2, confidence: 8, urgency: 8, risk: 2 } }],
|
||||||
|
evidence_next: [{ id: 'stringified-copy-brief', action: 'Stringified copy brief', evidence_needed: 'Does the copied result preserve this source trace?', source_item_id: 'stringified-preview-2', source_title: 'Stringified Build Order Preview' }],
|
||||||
|
set_aside: [{ id: 'stringified-dashboard', action: 'Stringified saved workspace dashboard', evidence_needed: 'Not before proof.', source_item_id: 'stringified-preview-3', source_title: 'Stringified Build Order Preview' }],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
mode: 'mvp',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
assert.equal(stringifiedRankerInputResponse.status, 200);
|
||||||
|
const stringifiedRankerInput = await stringifiedRankerInputResponse.json();
|
||||||
|
assert.equal(stringifiedRankerInput.input.embeddedPayloadSource, 'rankerInput');
|
||||||
|
assert.equal(stringifiedRankerInput.input.provenance.artifactId, 'SM-STRING-RANKER-INPUT-1');
|
||||||
|
assert.equal(stringifiedRankerInput.input.provenance.snapshotTitle, 'Stringified ranker input handoff');
|
||||||
|
assert.equal(stringifiedRankerInput.input.decisionContext.targetAudience, 'Tired non-AI-native solo builder');
|
||||||
|
assert.deepEqual(stringifiedRankerInput.input.decisionContext.nonGoals, ['Avoid saved workspace dashboards before source-traced handoff proof']);
|
||||||
|
assert.equal(stringifiedRankerInput.ranked[0].id, 'stringified-active-slice');
|
||||||
|
assert.equal(stringifiedRankerInput.ranked[0].provenance.sourceSection, 'ranker-input.buildOrderPreview.doFirst');
|
||||||
|
assert.equal(stringifiedRankerInput.ranked.find(item => item.id === 'stringified-dashboard').lane.id, 'park');
|
||||||
|
assert.equal(stringifiedRankerInput.handoff.readiness.status, 'ready');
|
||||||
|
assert.deepEqual(stringifiedRankerInput.handoff.warnings, []);
|
||||||
|
|
||||||
const gameRouteGuardrailResponse = await fetch(`${base}/api/rank-feedback`, {
|
const gameRouteGuardrailResponse = await fetch(`${base}/api/rank-feedback`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@@ -1863,7 +1901,7 @@ try {
|
|||||||
assert.equal(gameRouteGuardrail.handoff.readiness.status, 'ready');
|
assert.equal(gameRouteGuardrail.handoff.readiness.status, 'ready');
|
||||||
assert.deepEqual(gameRouteGuardrail.handoff.warnings, []);
|
assert.deepEqual(gameRouteGuardrail.handoff.warnings, []);
|
||||||
|
|
||||||
console.log(JSON.stringify({ ok: true, top: data.ranked[0].id, hintedTop: hinted.ranked[0].id, actionTop: actions.ranked[0].id, nestedConceptTop: nestedConcept.ranked[0].id, nonGoalTop: nonGoal.ranked[0].id, structuredContextTop: structuredContext.ranked[0].id, lensOnlyTop: lensOnly.ranked[0].id, scattermindPaidShapeTop: scattermindPaidShape.ranked[0].id, mergedContextTop: mergedContext.ranked[0].id, embeddedJsonTop: embeddedJson.ranked[0].id, fencedJsonTop: fencedJson.ranked[0].id, embeddedSnapshotTop: embeddedSnapshot.ranked[0].id, sourceExcerptTop: sourceExcerpt.ranked[0].id, snakeCaseBridgeTop: snakeCaseBridge.ranked[0].id, nextStepsAliasTop: nextStepsAlias.ranked[0].id, summaryGuardrailTop: summaryGuardrail.ranked[0].id, bridgeEnvelopeTop: bridgeEnvelope.ranked[0].id, directEnvelopeSectionsTop: directEnvelopeSections.ranked[0].id, softDirectLaneAliasesTop: softDirectLaneAliases.ranked[0].id, threadsFallbackTop: threadsFallback.ranked[0].id, questionsFallbackTop: questionsFallback.ranked[0].id, freeSnapshotTop: freeSnapshot.ranked[0].id, storedScattermindRowTop: storedScattermindRow.ranked[0].id, candidateActionsAliasTop: candidateActionsAlias.ranked[0].id, rankReadyActionsEnvelopeTop: rankReadyActionsEnvelope.ranked[0].id, continuationEnvelopeTop: continuationEnvelope.ranked[0].id, buildOrderPreviewTop: buildOrderPreview.ranked[0].id, gameRouteGuardrailTop: gameRouteGuardrail.ranked[0].id, duplicateIds: duplicateIds.ranked.map(item => item.id), readiness: data.handoff.readiness.status, provenance: data.input.provenance, buildOrder: data.buildOrder }, null, 2));
|
console.log(JSON.stringify({ ok: true, top: data.ranked[0].id, hintedTop: hinted.ranked[0].id, actionTop: actions.ranked[0].id, nestedConceptTop: nestedConcept.ranked[0].id, nonGoalTop: nonGoal.ranked[0].id, structuredContextTop: structuredContext.ranked[0].id, lensOnlyTop: lensOnly.ranked[0].id, scattermindPaidShapeTop: scattermindPaidShape.ranked[0].id, mergedContextTop: mergedContext.ranked[0].id, embeddedJsonTop: embeddedJson.ranked[0].id, fencedJsonTop: fencedJson.ranked[0].id, embeddedSnapshotTop: embeddedSnapshot.ranked[0].id, sourceExcerptTop: sourceExcerpt.ranked[0].id, snakeCaseBridgeTop: snakeCaseBridge.ranked[0].id, nextStepsAliasTop: nextStepsAlias.ranked[0].id, summaryGuardrailTop: summaryGuardrail.ranked[0].id, bridgeEnvelopeTop: bridgeEnvelope.ranked[0].id, directEnvelopeSectionsTop: directEnvelopeSections.ranked[0].id, softDirectLaneAliasesTop: softDirectLaneAliases.ranked[0].id, threadsFallbackTop: threadsFallback.ranked[0].id, questionsFallbackTop: questionsFallback.ranked[0].id, freeSnapshotTop: freeSnapshot.ranked[0].id, storedScattermindRowTop: storedScattermindRow.ranked[0].id, candidateActionsAliasTop: candidateActionsAlias.ranked[0].id, rankReadyActionsEnvelopeTop: rankReadyActionsEnvelope.ranked[0].id, continuationEnvelopeTop: continuationEnvelope.ranked[0].id, buildOrderPreviewTop: buildOrderPreview.ranked[0].id, stringifiedRankerInputTop: stringifiedRankerInput.ranked[0].id, gameRouteGuardrailTop: gameRouteGuardrail.ranked[0].id, duplicateIds: duplicateIds.ranked.map(item => item.id), readiness: data.handoff.readiness.status, provenance: data.input.provenance, buildOrder: data.buildOrder }, null, 2));
|
||||||
} finally {
|
} finally {
|
||||||
server.kill('SIGTERM');
|
server.kill('SIGTERM');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -520,8 +520,6 @@ function featureSetFrom(input = {}) {
|
|||||||
|| body.candidate_feature_set
|
|| body.candidate_feature_set
|
||||||
|| body.rankReadyFeatureSet
|
|| body.rankReadyFeatureSet
|
||||||
|| body.rank_ready_feature_set
|
|| body.rank_ready_feature_set
|
||||||
|| body.buildOrderPreview
|
|
||||||
|| body.build_order_preview
|
|
||||||
|| envelope.featureSet
|
|| envelope.featureSet
|
||||||
|| envelope.feature_set
|
|| envelope.feature_set
|
||||||
|| envelope.candidateSet
|
|| envelope.candidateSet
|
||||||
@@ -530,8 +528,6 @@ function featureSetFrom(input = {}) {
|
|||||||
|| envelope.candidate_feature_set
|
|| envelope.candidate_feature_set
|
||||||
|| envelope.rankReadyFeatureSet
|
|| envelope.rankReadyFeatureSet
|
||||||
|| envelope.rank_ready_feature_set
|
|| envelope.rank_ready_feature_set
|
||||||
|| envelope.buildOrderPreview
|
|
||||||
|| envelope.build_order_preview
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -699,14 +695,59 @@ function parseEmbeddedRankPayload(value = '') {
|
|||||||
|
|
||||||
function expandEmbeddedRankPayload(body = {}) {
|
function expandEmbeddedRankPayload(body = {}) {
|
||||||
const original = objectFrom(body);
|
const original = objectFrom(body);
|
||||||
for (const key of ['payload', 'rankPayload', 'scattermindPayload', 'conceptMapJson', 'concept_map_json', 'fullReadingJson', 'full_reading_json', 'fullReading', 'full_reading', 'idea', 'ideaText', 'optionsText']) {
|
const stringOnlyEmbeddedKeys = new Set([
|
||||||
|
'rankerInput',
|
||||||
|
'ranker_input',
|
||||||
|
'rankerHandoff',
|
||||||
|
'ranker_handoff',
|
||||||
|
'rankerBridge',
|
||||||
|
'ranker_bridge',
|
||||||
|
'rankReady',
|
||||||
|
'rank_ready',
|
||||||
|
'bridge',
|
||||||
|
'bridgePayload',
|
||||||
|
'bridge_payload',
|
||||||
|
'continuation',
|
||||||
|
'continuationPlan',
|
||||||
|
'continuation_plan',
|
||||||
|
]);
|
||||||
|
for (const key of [
|
||||||
|
'payload',
|
||||||
|
'rankPayload',
|
||||||
|
'scattermindPayload',
|
||||||
|
'rankerInput',
|
||||||
|
'ranker_input',
|
||||||
|
'rankerHandoff',
|
||||||
|
'ranker_handoff',
|
||||||
|
'rankerBridge',
|
||||||
|
'ranker_bridge',
|
||||||
|
'rankReady',
|
||||||
|
'rank_ready',
|
||||||
|
'bridge',
|
||||||
|
'bridgePayload',
|
||||||
|
'bridge_payload',
|
||||||
|
'continuation',
|
||||||
|
'continuationPlan',
|
||||||
|
'continuation_plan',
|
||||||
|
'conceptMapJson',
|
||||||
|
'concept_map_json',
|
||||||
|
'fullReadingJson',
|
||||||
|
'full_reading_json',
|
||||||
|
'fullReading',
|
||||||
|
'full_reading',
|
||||||
|
'idea',
|
||||||
|
'ideaText',
|
||||||
|
'optionsText',
|
||||||
|
]) {
|
||||||
const embedded = typeof original[key] === 'string'
|
const embedded = typeof original[key] === 'string'
|
||||||
? parseEmbeddedRankPayload(original[key])
|
? parseEmbeddedRankPayload(original[key])
|
||||||
: looksLikeRankPayload(original[key])
|
: !stringOnlyEmbeddedKeys.has(key) && looksLikeRankPayload(original[key])
|
||||||
? original[key]
|
? original[key]
|
||||||
: null;
|
: null;
|
||||||
if (!embedded) continue;
|
if (!embedded) continue;
|
||||||
const expanded = { ...original, ...embedded };
|
const expanded = stringOnlyEmbeddedKeys.has(key)
|
||||||
|
? { ...original, [key]: embedded }
|
||||||
|
: { ...original, ...embedded };
|
||||||
if (key === 'idea' && !embedded.idea && !embedded.ideaText) expanded.idea = '';
|
if (key === 'idea' && !embedded.idea && !embedded.ideaText) expanded.idea = '';
|
||||||
if (key === 'optionsText' && !embedded.optionsText) expanded.optionsText = '';
|
if (key === 'optionsText' && !embedded.optionsText) expanded.optionsText = '';
|
||||||
if (original.mode && !embedded.mode) expanded.mode = original.mode;
|
if (original.mode && !embedded.mode) expanded.mode = original.mode;
|
||||||
|
|||||||
Reference in New Issue
Block a user