Skip to content

feat(landing): config-first homepage on a single sales example#1076

Merged
buremba merged 2 commits into
mainfrom
feat/landing-consolidate
May 26, 2026
Merged

feat(landing): config-first homepage on a single sales example#1076
buremba merged 2 commits into
mainfrom
feat/landing-consolidate

Conversation

@buremba
Copy link
Copy Markdown
Member

@buremba buremba commented May 26, 2026

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/sales app (was ecommerce connector / finance reaction / office-bot skill). The page reads top-to-bottom as one pipeline: Salesforce connector → account/renewal entities → health watcher → reaction → agent + skill.

Structure

  • Config-first order: lobu.config.ts → Connectors → Watchers → Memory → Skills.
  • Dropped the interactive use-case tab strip from the index (it only half-swapped snippets). /for/<useCase> SEO pages are preserved — they still parameterize connector/memory/watcher via defaultUseCaseId.
  • Watchers layout: watcher (reactive + dreaming) moved under the copy on the left, reaction takes the right column — no more stacked 70-line column.
  • Trimmed repeated bullet lists across product sections.

Examples

  • New examples/sales/skills/account-brief/SKILL.md — lean skill exercising nixPackages, network.allow, and a per-domain egress judge (what the Skills section pitches).
  • Typed the Salesforce connector via the SDK generics (ConnectorRuntime<Checkpoint> / SyncContext<Checkpoint>); removed (ctx.checkpoint as any) and the r.json() as any.

Copy / CTA

  • Architecture heading → "Raw events in. Typed memory out. Agents act."
  • Added a Schedule a call button to the bottom CTA (reuses the existing ScheduleDialog). Hero stays self-serve (Copy setup prompt + GitHub).

Generator

  • gen-landing-snippets.ts: repinned the PINNED sources to sales; fixed trimSkillMarkdown which hardcoded the deliveroo basket policy — it now folds the real judge policy from whatever skill is pinned.

Verification

  • bun run build green — 86 pages incl. all /for/* SEO pages.
  • Driven in a browser: config-first order confirmed, single sales story (no deliveroo/stripe/finance), Schedule-a-call dialog opens, Watchers layout balanced.
  • Salesforce connector typechecks clean against @lobu/connector-sdk.

Notes

  • The DSL inconsistency between connectorFromFile("./x.connector.ts") and reaction: "./x.reaction.ts" is unchanged (the string path is the real defineWatcher API). Worth unifying later, but that's an SDK change, not a landing edit.

Summary by CodeRabbit

  • New Features

    • Added "Schedule a call" button to the landing page
    • New account-brief skill to produce pre-renewal briefs from public news sources
  • Updates

    • Reorganized landing page sections and refined marketing copy
    • Architecture diagram wording updated to "Raw events in. Typed memory out. Agents act."
    • Salesforce data connector improved for safer, more reliable incremental sync and checkpointing

Review Change Stack

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.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 84eb74b6-c092-44dd-bfeb-cbbf55e24480

📥 Commits

Reviewing files that changed from the base of the PR and between 4edbd69 and f4b148f.

📒 Files selected for processing (1)
  • packages/landing/scripts/gen-landing-snippets.ts

📝 Walkthrough

Walkthrough

PR 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.

Changes

Sales Example: Connector Type Safety & Account Brief Skill

Layer / File(s) Summary
Salesforce Connector Type Safety
examples/sales/salesforce-pipeline.connector.ts
SalesforcePipelineConnector extends ConnectorRuntime<Checkpoint> with typed Opportunity records; sync accepts SyncContext<Checkpoint>, derives since from ctx.checkpoint?.last_modified, parses typed records, and computes returned checkpoint from last fetched LastModifiedDate.
Account Brief Skill Definition
examples/sales/skills/account-brief/SKILL.md
Adds account-brief skill metadata, required tooling (jq), news-read judges and domain allowlist, explicit forbidden actions, a 4-step fetch/summarize/assess workflow, and recording of renewal-risk entities from public sources.

Landing Page Redesign & Generation

Layer / File(s) Summary
Landing Snippet Generation & Pinning
packages/landing/scripts/gen-landing-snippets.ts
PINNED selectors now source connector, reaction, and skill from the sales example; SKILL.md judge-policy trimming emits folded YAML headers and preserves up to two non-empty lines of each policy body.
Architecture Narrative Update
packages/landing/src/components/ArchitectureDiagram.tsx
Change header subheading to "Raw events in. Typed memory out. Agents act."
Schedule Call Button
packages/landing/src/components/CTA.tsx
Import and render ScheduleCallButton and ScheduleCallIcon, adding a "Schedule a call" action to the CTA.
Page Structure & Use Case Handling
packages/landing/src/components/LandingPage.tsx
Derive activeUseCase from props.defaultUseCaseId (defaults to "sales"), remove UseCaseTabs state, and reorder sections to surface Agents earlier and reposition Memory and Skills.
Connectors Section Redesign
packages/landing/src/components/LandingPage.tsx
Set product grid reverse, update connectors descriptive paragraph, and remove two connector feature-list items.
Memory & Watchers Sections Updates
packages/landing/src/components/LandingPage.tsx
Remove "Agent-assisted modeling" from MemorySection; remove "Reactions are optional" and "Auditable" from WatchersSection; rearrange guide/docs links and code panel layout.
Skills Section Updates
packages/landing/src/components/LandingPage.tsx
Update SkillsSection header comment and change example footer link slug from "office-bot" to "sales".
Agents Section Rewrite
packages/landing/src/components/LandingPage.tsx
Rework eyebrow/heading and feature bullets (remove "Durable & audited"), and change CodeBlock badge to lobu.config.ts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • lobu-ai/lobu#988: Overlaps with per-use-case snippet tabs and snippet-generation changes in gen-landing-snippets.ts and landing page use-case rendering logic.
  • lobu-ai/lobu#737: Shares landing page restructuring and defaultUseCaseId-driven behavior in LandingPage.tsx.
  • lobu-ai/lobu#744: Overlaps content edits for Memory/Skills sections and related landing UI changes.

"I'm a rabbit who types and fetches with care,
I map Opportunities with checkpoint-aware flair,
I brief accounts from headlines, public and bright,
And the landing page blossoms with schedule-call light,
Hopping through code with a tiny celebratory bite!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: reworking the homepage to be config-first and consolidated around a single sales example, which aligns with the core objective of the PR.
Description check ✅ Passed The description is comprehensive and covers the Summary, Test plan, and Notes sections as required by the template, though the Test plan section uses narrative description rather than checkboxes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/landing-consolidate

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Scope block-scalar trimming to judges policies 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

📥 Commits

Reviewing files that changed from the base of the PR and between e8c354b and 4edbd69.

⛔ Files ignored due to path filters (1)
  • packages/landing/src/generated/landing-snippets.json is excluded by !**/generated/**
📒 Files selected for processing (6)
  • examples/sales/salesforce-pipeline.connector.ts
  • examples/sales/skills/account-brief/SKILL.md
  • packages/landing/scripts/gen-landing-snippets.ts
  • packages/landing/src/components/ArchitectureDiagram.tsx
  • packages/landing/src/components/CTA.tsx
  • packages/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`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Comment on lines 19 to +20
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 ?? [];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

@buremba
Copy link
Copy Markdown
Member Author

buremba commented May 26, 2026

bug_free 88, simplicity 88, slop 5, bugs 0, 0 blockers

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.

Suggested fixes

File Line Change
packages/landing/scripts/gen-landing-snippets.ts 27 Replace the stale “interactive use-case tab strip” sentence with “/for/ pages use these route-specific snippets; the homepage stays pinned to sales.”
packages/landing/scripts/gen-landing-snippets.ts 47 Rename “per-use-case tab data” to “per-use-case route data” because the tab UI was removed.
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 pi-review commit status. See docs/REVIEW_SCHEMA.md.

@buremba buremba merged commit 9615eff into main May 26, 2026
18 of 20 checks passed
@buremba buremba deleted the feat/landing-consolidate branch May 26, 2026 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants