feat(landing): config-first homepage on a single sales example#1076
Conversation
Consolidate the homepage so every primitive snippet comes from one coherent app (examples/sales) instead of four unrelated ones, and lead with lobu.config.ts. - Pin connector/reaction/skill snippets to sales (was ecommerce/finance/ office-bot); homepage now reads as one story: Salesforce connector -> account/renewal entities -> health watcher -> reaction -> agent + skill. - Reorder sections config-first: lobu.config.ts -> Connectors -> Watchers -> Memory -> Skills. Drop the interactive use-case tab strip from the index; /for/<useCase> SEO pages keep their per-use-case snippets. - Add examples/sales/skills/account-brief (lean SKILL.md exercising nixPackages + network.allow + per-domain judge). - Type the Salesforce connector via the SDK generic (ConnectorRuntime<C> / SyncContext<C>); drop the (ctx.checkpoint as any) and json `as any`. - Watchers layout: watcher on the left under the copy, reaction on the right, instead of stacking both (saves vertical space). - Fix trimSkillMarkdown: fold the real judge policy instead of the hardcoded deliveroo text. - Architecture heading -> "Raw events in. Typed memory out. Agents act." - Add a Schedule a call CTA (existing ScheduleDialog) to the bottom CTA. - Trim repeated bullet lists across product sections.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughPR updates the sales example connector with typed checkpoints and Opportunity records, adds an account-brief skill, and redesigns the landing site: removes use-case tabs, pins snippets to the sales example, adds a schedule-call CTA, changes SKILL.md policy trimming, and refreshes copy and section order. ChangesSales Example: Connector Type Safety & Account Brief Skill
Landing Page Redesign & Generation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/landing/scripts/gen-landing-snippets.ts (1)
348-371:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winScope block-scalar trimming to
judgespolicies only.This matcher currently rewrites/truncates any frontmatter block scalar, not just judge policies. That can silently trim unrelated fields if they use
>/|.Proposed fix
function trimSkillMarkdown(raw: string): string { const lines = raw.split("\n"); if (lines[0]?.trim() !== "---") return raw; @@ const out: string[] = []; let i = 0; + let judgesIndent: number | null = null; while (i < fm.length) { const line = fm[i]; + const lineTrim = line.trimStart(); + const lineIndent = line.length - lineTrim.length; + + if (/^judges:\s*$/.test(lineTrim)) { + judgesIndent = lineIndent; + out.push(line); + i++; + continue; + } + if ( + judgesIndent !== null && + lineTrim !== "" && + lineIndent <= judgesIndent && + !/^judges:\s*$/.test(lineTrim) + ) { + judgesIndent = null; + } @@ - const blockScalar = /^(\s*)([\w-]+):\s*[>|][+-]?\s*$/.exec(line); - if (blockScalar) { + const blockScalar = /^(\s*)([\w-]+):\s*[>|][+-]?\s*$/.exec(line); + if (blockScalar && judgesIndent !== null && blockScalar[1].length > judgesIndent) { const baseIndent = blockScalar[1].length; const policyName = blockScalar[2]; out.push(`${" ".repeat(baseIndent)}${policyName}: >`);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/landing/scripts/gen-landing-snippets.ts` around lines 348 - 371, The current blockScalar matcher rewrites any YAML block scalar; change it to only apply the truncation when the scalar's key is the judges policy: after extracting blockScalar and policyName, add a guard that proceeds with the rewriting/truncation logic only if policyName === "judges" (otherwise leave the original line untouched and continue normally). Keep the existing variables/flow (baseIndent, kept, j, i) for the judges case so only judges block-scalars are truncated while other >/| fields remain unmodified.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/sales/salesforce-pipeline.connector.ts`:
- Line 18: The query string q used to fetch Opportunities lacks deterministic
ordering before deriving the next checkpoint from records.at(-1), so add an
explicit ORDER BY LastModifiedDate ASC (and include a unique tiebreaker like Id
ASC) to the SOQL in the places building q (the SELECT ... FROM Opportunity WHERE
... LIMIT 200 occurrences) so that records.at(-1) consistently represents the
oldest streamed row; update both query constructions (the q variable instances)
to include "ORDER BY LastModifiedDate ASC, Id ASC" before LIMIT.
- Around line 19-20: Before calling await r.json(), check the HTTP response
status by using r.ok on the response returned from the fetch call (variable r)
and throw or return a clear error that includes response.status and
response.statusText (and optionally await r.text() for body) so failures are not
masked by json parsing; update the fetch-handling around the const r = await
fetch(...) and the subsequent records extraction to guard on r.ok and provide
contextual error information before attempting to parse JSON.
---
Outside diff comments:
In `@packages/landing/scripts/gen-landing-snippets.ts`:
- Around line 348-371: The current blockScalar matcher rewrites any YAML block
scalar; change it to only apply the truncation when the scalar's key is the
judges policy: after extracting blockScalar and policyName, add a guard that
proceeds with the rewriting/truncation logic only if policyName === "judges"
(otherwise leave the original line untouched and continue normally). Keep the
existing variables/flow (baseIndent, kept, j, i) for the judges case so only
judges block-scalars are truncated while other >/| fields remain unmodified.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b91da966-1f89-43d4-a971-7edfd5c4ac1d
⛔ Files ignored due to path filters (1)
packages/landing/src/generated/landing-snippets.jsonis excluded by!**/generated/**
📒 Files selected for processing (6)
examples/sales/salesforce-pipeline.connector.tsexamples/sales/skills/account-brief/SKILL.mdpackages/landing/scripts/gen-landing-snippets.tspackages/landing/src/components/ArchitectureDiagram.tsxpackages/landing/src/components/CTA.tsxpackages/landing/src/components/LandingPage.tsx
| const since = (ctx.checkpoint as any)?.last_modified ?? "2000-01-01T00:00:00Z"; | ||
| async sync(ctx: SyncContext<Checkpoint>) { | ||
| const since = ctx.checkpoint?.last_modified ?? "2000-01-01T00:00:00Z"; | ||
| const q = `SELECT Id,Name,StageName,LastModifiedDate FROM Opportunity WHERE LastModifiedDate > ${since} LIMIT 200`; |
There was a problem hiding this comment.
Add deterministic ordering before deriving the next checkpoint.
Checkpointing from records.at(-1) is unsafe without ORDER BY LastModifiedDate ASC; API return order is not guaranteed, which can skip or replay events across sync runs.
Suggested fix
- const q = `SELECT Id,Name,StageName,LastModifiedDate FROM Opportunity WHERE LastModifiedDate > ${since} LIMIT 200`;
+ const q = `SELECT Id,Name,StageName,LastModifiedDate FROM Opportunity WHERE LastModifiedDate > ${since} ORDER BY LastModifiedDate ASC LIMIT 200`;Also applies to: 29-29
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/sales/salesforce-pipeline.connector.ts` at line 18, The query string
q used to fetch Opportunities lacks deterministic ordering before deriving the
next checkpoint from records.at(-1), so add an explicit ORDER BY
LastModifiedDate ASC (and include a unique tiebreaker like Id ASC) to the SOQL
in the places building q (the SELECT ... FROM Opportunity WHERE ... LIMIT 200
occurrences) so that records.at(-1) consistently represents the oldest streamed
row; update both query constructions (the q variable instances) to include
"ORDER BY LastModifiedDate ASC, Id ASC" before LIMIT.
| const r = await fetch(`${ctx.config.instance_url}/services/data/v60.0/query?q=${encodeURIComponent(q)}`); | ||
| const records: any[] = (await r.json() as any).records ?? []; | ||
| const records = ((await r.json()) as { records?: Opportunity[] }).records ?? []; |
There was a problem hiding this comment.
Handle non-2xx responses before JSON parsing.
await r.json() on failed/non-JSON responses can throw and hide the actual failure mode. Add an r.ok guard with clear error context.
Suggested fix
const r = await fetch(`${ctx.config.instance_url}/services/data/v60.0/query?q=${encodeURIComponent(q)}`);
+ if (!r.ok) {
+ throw new Error(`Salesforce query failed: ${r.status} ${r.statusText}`);
+ }
const records = ((await r.json()) as { records?: Opportunity[] }).records ?? [];🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/sales/salesforce-pipeline.connector.ts` around lines 19 - 20, Before
calling await r.json(), check the HTTP response status by using r.ok on the
response returned from the fetch call (variable r) and throw or return a clear
error that includes response.status and response.statusText (and optionally
await r.text() for body) so failures are not masked by json parsing; update the
fetch-handling around the const r = await fetch(...) and the subsequent records
extraction to guard on r.ok and provide contextual error information before
attempting to parse JSON.
|
bug_free 88, simplicity 88, slop 5, bugs 0, 0 blockers Script suites passed: typecheck/unit/integration exits 0. Explored with Suggested fixes
Full verdict JSON{
"bug_free_confidence": 88,
"bugs": 0,
"slop": 5,
"simplicity": 88,
"blockers": [],
"change_type": "feat",
"behavior_change_risk": "low",
"tests_adequate": true,
"suggested_fixes": [
{
"file": "packages/landing/scripts/gen-landing-snippets.ts",
"line": 27,
"change": "Replace the stale “interactive use-case tab strip” sentence with “/for/<useCase> pages use these route-specific snippets; the homepage stays pinned to sales.”"
},
{
"file": "packages/landing/scripts/gen-landing-snippets.ts",
"line": 47,
"change": "Rename “per-use-case tab data” to “per-use-case route data” because the tab UI was removed."
}
],
"notes": "Script suites passed: typecheck/unit/integration exits 0. Explored with `cd packages/landing && bun run build`; build generated 90 pages successfully. Checked dist/index.html for the config-first copy, Salesforce snippet, and Schedule a call button. No backend/DB exploration needed for landing/example-only diff.",
"categories": {
"src": 170,
"tests": 0,
"docs": 40,
"config": 0,
"deps": 0,
"migrations": 0,
"ci": 0,
"generated": 20
}
}Local review gate — branch protection can require the |
What
Reworks the homepage so it tells one coherent story, config-first, instead of stitching snippets from four unrelated example apps.
Consolidation
Every primitive snippet now comes from the single
examples/salesapp (wasecommerceconnector /financereaction /office-botskill). The page reads top-to-bottom as one pipeline: Salesforce connector → account/renewal entities → health watcher → reaction → agent + skill.Structure
lobu.config.ts→ Connectors → Watchers → Memory → Skills./for/<useCase>SEO pages are preserved — they still parameterize connector/memory/watcher viadefaultUseCaseId.reactive + dreaming) moved under the copy on the left, reaction takes the right column — no more stacked 70-line column.Examples
examples/sales/skills/account-brief/SKILL.md— lean skill exercisingnixPackages,network.allow, and a per-domain egress judge (what the Skills section pitches).ConnectorRuntime<Checkpoint>/SyncContext<Checkpoint>); removed(ctx.checkpoint as any)and ther.json() as any.Copy / CTA
ScheduleDialog). Hero stays self-serve (Copy setup prompt + GitHub).Generator
gen-landing-snippets.ts: repinned thePINNEDsources tosales; fixedtrimSkillMarkdownwhich hardcoded the deliveroo basket policy — it now folds the real judge policy from whatever skill is pinned.Verification
bun run buildgreen — 86 pages incl. all/for/*SEO pages.@lobu/connector-sdk.Notes
connectorFromFile("./x.connector.ts")andreaction: "./x.reaction.ts"is unchanged (the string path is the realdefineWatcherAPI). Worth unifying later, but that's an SDK change, not a landing edit.Summary by CodeRabbit
New Features
Updates