Release dev → main: card claim + booking + mobile /create fixes, suggested people/posts, CI/CD - #7
Merged
Merged
Conversation
persona.zynd.ai, persona.api.zynd.ai, cards.zynd.ai, cards.api.zynd.ai and api.zynd.ai each get a site block; persona.zynd.ai keeps /api/* -> persona-api so the OAuth callbacks and A2A URLs registered on that name still resolve, and api.zynd.ai keeps the legacy /cards /ask /onboard /v1 paths from infra/api-box/Caddyfile so existing clients survive a cutover. Pin Caddy to Let's Encrypt (acme_ca): the Ubuntu-packaged Caddy 2.6.2 tries ZeroSSL first and its legacy EAB endpoint now answers 422 caddy_legacy_user_removed, so every issuance attempt failed before it ever reached a challenge, whatever DNS said. README gets the domain-to-env map and a cutover caveat (a fresh memory DB is not the live one). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
cards.zynd.ai's `/` was a placeholder hero, while the real landing page (the dashboard's /agent-card) sat at /agent-card inside a root layout that also rendered <html>, so it never looked like the dashboard version. Mirror the dashboard's layout: the landing page gets its own standalone root layout at `/` (route group `(landing)`), and every other page moves into a `(site)` group that keeps the existing root layout (globals.css, zynd-ui.css, Providers). URLs are unchanged. The landing page code is identical to the dashboard's except the LinkedIn-only sign-in and the cards.zynd.ai metadataBase, both intentional for cards. /agent-card now 308-redirects to /, and the auth-bar / next-cookie fallbacks that pointed at /agent-card now point at /. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Deploying the single box by hand means remembering which services a change touches and in what order to build and restart them. deploy.sh works that out from the git diff between the old and new commit, rebuilds only the affected services, and health-checks them. Safety properties it is built around: it only deploys commits reachable from origin/main, refuses to run over local modifications, restores the previous .next if a web build fails so the old version keeps serving, and never applies database migrations or touches Caddy/DNS (those stay a person's call, per AGENTS.md section 6). `--plan` shows what would change without touching anything, and `<sha>` rolls back to an earlier commit. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ures AGENTS.md section 5 lists known, pre-existing failures per service, and `npm run lint` already exits 1 on main, so a plain test/lint gate would be red from day one and get ignored. These two scripts turn the baseline into a ratchet: CI passes while the failure/error count is at or below the recorded number and fails the moment it goes above it, so existing debt can't grow silently. The number is meant to be lowered as failures get fixed, never raised to make a build green. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ci.yml: web lint/typecheck/build and the three Python suites on PRs and pushes to dev. Main has pre-existing failures (AGENTS.md section 5 baselines, plus 24 and 3 ESLint errors in persona-web and cards-web that AGENTS.md doesn't list), so both gates are ratchets that fail only on new ones; a plain gate would be red from day one. Baselines were measured on a clean clone with no .env, using the exact install commands the workflow runs, and each gate script was shown to pass at the baseline and fail one below it. deploy-single-box.yml: on push to main, run ci.yml, then SSH to the box and run infra/single-box/deploy.sh, which rebuilds and restarts only the services whose files changed and health-checks them. A failed web build restores the previous .next and restarts nothing. Manual runs can roll back to a commit on main. The SSH key on the box is meant to be a forced-command key that can run only deploy.sh; the one-time setup (that key, the docker group, three repo secrets) is a person's step and is written up in infra/single-box/README.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n old Next The first real CI run failed on persona-web's build with "next/font/google queries have exactly one entry" (Geist, Turbopack). It does not reproduce on a Mac or on the Linux server: Google returned plain gstatic URLs to both, and persona-web builds there with the same lockfile. cards-web, on next@16.3.6, built fine on the same runner in the same run, and pulls Geist too. So it is Turbopack's font handler in persona-web's exactly-pinned next@16.2.1 meeting whatever Google returned to that runner. Try Turbopack first (what production uses), then retry with webpack, which passes on 16.2.1. A real code error fails both and stays red; only the bundler bug is let through, with a warning annotation. Bumping persona-web's Next to match cards-web is the real fix and is left to whoever owns that app. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The box now hosts two environments off the same code: main -> prod, dev -> dev, sharing the same databases for now. Dev gets its own checkout (/home/ubuntu/zynd-platform-dev), pm2 apps (api-dev, web-dev, cards-web-dev on 127.0.0.1:8100/3101/3102), containers in a separate compose project on prod's network (memory API+MCP on 8101/8190, cards-api on 8102), and dev.* Caddy site blocks. No dev memory worker on purpose: its nightly cron jobs (decay, resolution, recompute, orphan cleanup) would run twice on the shared database, so the dev API shares prod's worker and Redis. deploy.sh takes --env=dev|prod and keeps all per-environment settings in one block; the deploy workflow now deploys on pushes to dev as well as main (CI is called by it, so CI no longer triggers on push to avoid checking twice), and a manual run picks the environment. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The first run that reached the SSH step failed with "No ED25519 host key is known for *** and you have requested strict checking": the DEPLOY_HOST_KEY secret held something that wasn't a usable key line. Accept the key from whatever was pasted (a leading host name, a trailing comment, quotes, CRLF, or just the base64), and otherwise fail with a message that says what a valid value looks like. Also check the private key is intact without printing it, and compare the pinned key with what the server actually presents, reporting both fingerprints on a mismatch. README: document the two environments (main -> prod, dev -> dev), what "same DB for now" means, and the --env option. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
added suggestion people and suggested posts
…booking link Claiming was invisible: a creator visiting their own anonymously-published card saw a generic "Sign In", and after signing in the claim ran silently in the background with no feedback and no Edit button until a reload. The header now shows "Claim this card" to whoever holds the card's one-time claim token (only the publishing browser does). Clicking it signs in with LinkedIn and finishes the claim on return (intent kept in sessionStorage across the OAuth round-trip); signed-in creators get the same button, and on success the page refreshes into the owner view. The unused UnclaimedCardActions component is removed (it also carried one of the baseline lint errors). Server-side claim rules are unchanged. Booking: onboarding's "Got a Calendly?" becomes "Got a booking link?" with Calendly and Google Calendar inputs; the edit page gets both too, and the profile's Book-a-call card shows one CTA per link. Google links go in a new google_calendar_url field rather than reusing calendly_url, because the card JSON is read by AI agents and a mislabelled field would mislead them. Both booking fields are normalized (missing https:// added, non-http schemes dropped) so "calendly.com/me" no longer silently hides the card. The card is stored as JSON, so no schema migration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The resume export drew GitHub, LinkedIn and the other profile links as plain text, so a recruiter reading the PDF had to retype them. Each header link is now its own link annotation (same look, same whole-link wrapping), as are a project's URL (when the line isn't cut off) and the footer's live-profile link. Only http(s) URLs get an annotation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e work on phones Review screen: the platform label beside each scraped post was a fixed 26px column, which fits "X" but not "LINKEDIN", so the label ran into the post text. It's now 60px (ellipsis past that) and sits on its own line on phones. Mobile: below 1040px the grid stacked but the root kept its fixed 100dvh height with an internally scrolling shell, so on a phone the form/questions were squeezed into a ~190px scrolling window and the footer overflowed sideways. The stacked layout now grows and scrolls with the page. The 640px block also gets 16px inputs (iOS Safari zooms on focus below that), >=40px tap targets, wrapping/ellipsis for long URLs and handles, wrapping button rows, tighter paddings, a stacked footer, and the handle prefix above its input. Desktop (>1040px) is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bind address After LinkedIn sign-in the callback redirected to https://localhost:3102/… on dev (https://0.0.0.0:3002/… on prod): behind Caddy, `next start` builds request.url from its own listen address, and the route used that origin. It now takes the host the browser actually used (X-Forwarded-Host / Host, which Caddy sets and overwrites from clients), falling back to request.url when there's no proxy (`next dev`) or the header isn't a plain host[:port]. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…LLM's rewrite A LinkedIn URL like /in/dilnawaz-hossain-asrafi-798a6a228/ scraped fine, but the card (and the resume export) showed /in/dilnawaz-hossain-asrafi: synthesis returns its own identity.links, the LLM "tidied" the slug, and the pipeline only setdefault()-ed the real URL, so the guess won. That also broke refresh-linkedin, which re-scrapes the stored link. The LinkedIn and X URLs the user pasted (when they scraped) now overwrite the LLM's links, minus share-sheet query strings; GitHub's link comes from the API user. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat: integrate QuickEnrich API for suggested people functionality
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes
dev(717ef4b) tomain. Merging auto-deploys prod (cards.zynd.ai) throughdeploy-single-box.yml; the deploy rebuilds cards-web and the cards-api container only. Persona, memory, pm2 config and Caddy aren't touched.What ships
Cards: user-facing fixes and features
UnclaimedCardActionscomponent.https://0.0.0.0:3002/...after LinkedIn login.google_calendar_url. Booking URLs get normalized:https://is added when missing and non-http schemes are dropped.-798a6a228and broke refresh-linkedin. GitHub's link comes from the API./createworks on phones (natural page scroll, 16px inputs, 40px tap targets, wrapping rows). The LinkedIn label no longer overlaps post text on the review screen.Suggested people / posts (saraffa13, #5, #6)
GET /cards/by-handle/{handle}/suggested-postsand/suggested-people, using thecards.keyword_postsdaily cache.QUICKENRICH_*keys unset it returns[], it doesn't error.Infra / CI (AtmegaBuzz)
deploy.sh, auto-deploy ofmain→ prod anddev→ dev).dev.*domains, Caddy routes, and the landing page as the cards-web site root.Verified on 717ef4b
pytest: 142 passed, 2 failed (the knowntest_x_bot.pybaseline).tsc --noEmitclean,next buildpasses with prod's env (NEXT_PUBLIC_SITE_URL=https://cards.zynd.ai)./,/create,/directory,/find,/search,llms.txtandsitemap.xmlreturn 200.package-lock.jsonandrequirements.txtunchanged for cards-web and cards-api).Before / after merging
cards.keyword_posts(migration0003) already exists in aafo. Make sure0003is recorded as applied inpackages/dbsodb:migratedoesn't try to create it again.OPENAI_API_KEY(cards-api): without it/askreturns 500 and new cards publish without embeddings, so they're unsearchable.GITHUB_TOKEN: unauthenticated GitHub scraping is limited to 60 requests/hour, shared by dev and prod on this box.QUICKENRICH_BASE_URL,QUICKENRICH_API_KEY: QuickEnrich suggestions stay off without them.INDEXNOW_KEY,BING_API_KEY: no search-engine pings without them.cards.zynd.aionce. If Supabase refuses the redirect, addhttps://cards.zynd.ai/auth/callbackto the Supabase Auth redirect allowlist.tail -f /home/ubuntu/.zynd-box/deploy.log. Roll back withdeploy.sh 582b2a8.🤖 Generated with Claude Code