From 406801e6bf975d7ae1a0ede1277c55af0506f190 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Sat, 18 Jul 2026 17:15:35 +0800 Subject: [PATCH 1/4] Remove engagement pricing --- src/pages/WorkWithUsPage.vue | 3 +-- src/support/content-types.ts | 1 - storage/fixtures/work-with-us-page.json | 12 ++++-------- tests/pages/WorkWithUsPage.test.ts | 11 +++++++++++ 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/pages/WorkWithUsPage.vue b/src/pages/WorkWithUsPage.vue index 93e24364..94bdd5fd 100644 --- a/src/pages/WorkWithUsPage.vue +++ b/src/pages/WorkWithUsPage.vue @@ -51,8 +51,7 @@ {{ item }}

You leave with: {{ engagement.outcome }}

-

{{ engagement.price }}

- {{ engagement.cta.label }} + {{ engagement.cta.label }}
diff --git a/src/support/content-types.ts b/src/support/content-types.ts index e8095757..28bb3ec4 100644 --- a/src/support/content-types.ts +++ b/src/support/content-types.ts @@ -204,7 +204,6 @@ export interface WorkWithUsPageContent { includesLabel: string; includes: string[]; outcome: string; - price: string; cta: RouteLink; }>; faq: { diff --git a/storage/fixtures/work-with-us-page.json b/storage/fixtures/work-with-us-page.json index 6b3d785a..c7039d29 100644 --- a/storage/fixtures/work-with-us-page.json +++ b/storage/fixtures/work-with-us-page.json @@ -39,8 +39,7 @@ "— Two 90-minute working sessions with the founding/engineering team" ], "outcome": "A clear, honest answer to where AI fits in your stack — and an architectural plan that survives contact with production.", - "price": "From $8,000 · Fixed fee", - "cta": { "label": "START A CONVERSATION →", "to": "/contact" } + "cta": { "label": "More info", "to": "/contact" } }, { "label": "[ 02 ] Fractional AI Architect", @@ -59,8 +58,7 @@ "— Quarterly roadmap refresh aligned to business goals" ], "outcome": "Senior architectural oversight without a full-time hire. A team that gets better at building as the engagement runs.", - "price": "From $8,000/mo · 3-month minimum", - "cta": { "label": "START A CONVERSATION →", "to": "/contact" } + "cta": { "label": "More info", "to": "/contact" } }, { "label": "[ 03 ] Production Hardening", @@ -79,8 +77,7 @@ "— 2-week async support window post-engagement" ], "outcome": "AI features that hold under real conditions. Documentation your team owns. No dependency on us to keep it running.", - "price": "From $15,000 · Scoped after a paid discovery session ($2,500)", - "cta": { "label": "START A CONVERSATION →", "to": "/contact" } + "cta": { "label": "More info", "to": "/contact" } }, { "label": "[ 04 ] AI-Era Team Enablement", @@ -97,8 +94,7 @@ "— A written playbook the team owns after we leave" ], "outcome": "Engineers who can build with AI and review what it produces with real judgment. The capability stays in your team.", - "price": "From $6,000 · 4-week programme", - "cta": { "label": "START A CONVERSATION →", "to": "/contact" } + "cta": { "label": "More info", "to": "/contact" } } ], "faq": { diff --git a/tests/pages/WorkWithUsPage.test.ts b/tests/pages/WorkWithUsPage.test.ts index f8be2c3f..d20f69e9 100644 --- a/tests/pages/WorkWithUsPage.test.ts +++ b/tests/pages/WorkWithUsPage.test.ts @@ -22,4 +22,15 @@ describe('WorkWithUsPage', () => { expect(wrapper.text()).toContain(workWithUsPageContent.faq.items[0].question); expect(wrapper.text()).toContain(workWithUsPageContent.cta.button.label); }); + + it('replaces engagement prices with contact links', () => { + const wrapper = mount(WorkWithUsPage, { global }); + const moreInfoLinks = wrapper.findAll('a').filter((link) => link.text() === 'More info'); + + expect(moreInfoLinks).toHaveLength(workWithUsPageContent.engagements.length); + moreInfoLinks.forEach((link) => { + expect(link.attributes('to')).toBe('/contact'); + }); + expect(wrapper.text()).not.toMatch(/\$[\d,]+/); + }); }); From b6f82bba8308ccfb775a5668bb90d2ebcdc08a99 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Sat, 18 Jul 2026 17:19:34 +0800 Subject: [PATCH 2/4] Update founder bio link and metadata --- index.html | 7 ++++++- src/pages/AboutPage.vue | 2 +- src/support/content-types.ts | 4 ++++ src/support/seo.ts | 5 +++++ storage/fixtures/about-page.json | 4 ++-- storage/fixtures/site.json | 6 +++++- tests/pages/AboutPage.test.ts | 4 +++- tests/support/seo-content.test.ts | 10 ++++++++++ 8 files changed, 36 insertions(+), 6 deletions(-) diff --git a/index.html b/index.html index 526507d7..fbcb8a4c 100644 --- a/index.html +++ b/index.html @@ -75,7 +75,12 @@ "logo": "https://oullin.io/brand/logo-touch.png", "image": "https://oullin.io/brand/logo-touch.png", "description": "Oullin is a boutique software engineering and architecture consultancy for startups and scale-ups navigating the AI era. AI architecture, modernisation, and resilient systems for regulated and high-trust environments.", - "sameAs": ["https://github.com/oullin"] + "sameAs": ["https://github.com/oullin"], + "founder": { + "@type": "Person", + "name": "Gustavo Ocanto", + "url": "https://gocanto.sh" + } } ] diff --git a/src/pages/AboutPage.vue b/src/pages/AboutPage.vue index 3b7ed20f..cda5a660 100644 --- a/src/pages/AboutPage.vue +++ b/src/pages/AboutPage.vue @@ -16,7 +16,7 @@
{{ sidebar.founder.name }}

{{ sidebar.founder.copy }}

diff --git a/src/support/content-types.ts b/src/support/content-types.ts index 28bb3ec4..8ce93a62 100644 --- a/src/support/content-types.ts +++ b/src/support/content-types.ts @@ -30,6 +30,10 @@ export interface SiteContent { organization: { description: string; sameAs: string[]; + founder: { + name: string; + url: string; + }; }; nav: { links: RouteLink[]; diff --git a/src/support/seo.ts b/src/support/seo.ts index b421664d..d3cd097e 100644 --- a/src/support/seo.ts +++ b/src/support/seo.ts @@ -26,6 +26,11 @@ export const ORGANIZATION_JSON_LD = { image: siteUrlFor(SITE_LOGO), description: siteContent.organization.description, sameAs: siteContent.organization.sameAs, + founder: { + '@type': 'Person', + name: siteContent.organization.founder.name, + url: siteContent.organization.founder.url, + }, }; type TwitterCard = 'summary' | 'summary_large_image' | 'app' | 'player'; diff --git a/storage/fixtures/about-page.json b/storage/fixtures/about-page.json index dcf32b80..f64aa203 100644 --- a/storage/fixtures/about-page.json +++ b/storage/fixtures/about-page.json @@ -12,8 +12,8 @@ "label": "// founder & lead architect", "name": "Gustavo Ocanto", "copy": "Engineering leader with 20+ years across software engineering, consulting, architecture, AI-first products and companies, and technical management. 10+ years in banking and fintech.", - "linkLabel": "LinkedIn Profile →", - "linkUrl": "https://www.linkedin.com/in/gocanto/" + "linkLabel": "Founder bio →", + "linkUrl": "https://gocanto.sh" }, "proof": { "label": "// proof", diff --git a/storage/fixtures/site.json b/storage/fixtures/site.json index a15012a5..a750ff38 100644 --- a/storage/fixtures/site.json +++ b/storage/fixtures/site.json @@ -23,7 +23,11 @@ }, "organization": { "description": "Oullin is a boutique software engineering and architecture consultancy for startups and scale-ups navigating the AI era. AI architecture, modernisation, and resilient systems for regulated and high-trust environments.", - "sameAs": ["https://github.com/oullin"] + "sameAs": ["https://github.com/oullin"], + "founder": { + "name": "Gustavo Ocanto", + "url": "https://gocanto.sh" + } }, "nav": { "links": [ diff --git a/tests/pages/AboutPage.test.ts b/tests/pages/AboutPage.test.ts index 380bc114..4fffe242 100644 --- a/tests/pages/AboutPage.test.ts +++ b/tests/pages/AboutPage.test.ts @@ -75,7 +75,9 @@ describe('AboutPage', () => { expect(wrapper.text()).toContain(aboutPageContent.sidebar.founder.copy); expect(wrapper.text()).toContain(aboutPageContent.sidebar.proof.items[1]); expect(wrapper.find('[data-testid="recommendation-partial"]').text()).toContain('Recommendations partial'); - expect(wrapper.html()).toContain(aboutPageContent.sidebar.founder.linkUrl); + const founderLink = wrapper.find(`a[href="${aboutPageContent.sidebar.founder.linkUrl}"]`); + expect(founderLink.text()).toBe('Founder bio →'); + expect(founderLink.attributes('rel')).toContain('me'); }); it('renders the about footer without the skills marquee band', async () => { diff --git a/tests/support/seo-content.test.ts b/tests/support/seo-content.test.ts index 3903e621..56897189 100644 --- a/tests/support/seo-content.test.ts +++ b/tests/support/seo-content.test.ts @@ -9,6 +9,7 @@ import { siteContent } from '@/support/content.ts'; import { termsAndPoliciesPageContent } from '@/support/content/terms-and-policies-page.ts'; import { workWithUsPageContent } from '@/support/content/work-with-us-page.ts'; import { writingPageContent } from '@/support/content/writing-page.ts'; +import { ORGANIZATION_JSON_LD } from '@/support/seo.ts'; const indexHtml = readFileSync(resolve(process.cwd(), 'index.html'), 'utf8'); const staleGustavoTitle = 'Gustavo Ocanto - Engineering, Leadership, Fintech & eCommerce | Software Engineer, Architect, AI , AI Architect & Manager'; @@ -46,6 +47,15 @@ describe('SEO content fixtures', () => { expect(writingPageContent.seo.description).toContain('engineering judgment'); expect(termsAndPoliciesPageContent.seo.description).toContain("Oullin's terms and policies"); }); + + it('connects the organization founder metadata to the canonical bio site', () => { + expect(ORGANIZATION_JSON_LD.founder).toEqual({ + '@type': 'Person', + name: 'Gustavo Ocanto', + url: 'https://gocanto.sh', + }); + expect(indexHtml).toContain('"url": "https://gocanto.sh"'); + }); }); describe('index.html seo defaults', () => { From dda87f290df631abb19fa50bdd79e64a970834f7 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Sat, 18 Jul 2026 17:29:33 +0800 Subject: [PATCH 3/4] Address automated review feedback --- src/pages/WorkWithUsPage.vue | 2 +- tests/pages/WorkWithUsPage.test.ts | 5 +++-- tests/support/seo-content.test.ts | 8 +++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pages/WorkWithUsPage.vue b/src/pages/WorkWithUsPage.vue index 94bdd5fd..27c19e45 100644 --- a/src/pages/WorkWithUsPage.vue +++ b/src/pages/WorkWithUsPage.vue @@ -51,7 +51,7 @@ {{ item }}

You leave with: {{ engagement.outcome }}

- {{ engagement.cta.label }} + {{ engagement.cta.label }}
diff --git a/tests/pages/WorkWithUsPage.test.ts b/tests/pages/WorkWithUsPage.test.ts index d20f69e9..91d726c3 100644 --- a/tests/pages/WorkWithUsPage.test.ts +++ b/tests/pages/WorkWithUsPage.test.ts @@ -28,9 +28,10 @@ describe('WorkWithUsPage', () => { const moreInfoLinks = wrapper.findAll('a').filter((link) => link.text() === 'More info'); expect(moreInfoLinks).toHaveLength(workWithUsPageContent.engagements.length); - moreInfoLinks.forEach((link) => { + moreInfoLinks.forEach((link, index) => { expect(link.attributes('to')).toBe('/contact'); + expect(link.attributes('aria-label')).toBe(`More info about ${workWithUsPageContent.engagements[index]?.label}`); }); - expect(wrapper.text()).not.toMatch(/\$[\d,]+/); + expect(wrapper.text()).not.toMatch(/(?:[$€£]\s*\d[\d,.]*(?:[km])?|\b\d[\d,.]*(?:[km])?\s*(?:USD|EUR|GBP)\b)/i); }); }); diff --git a/tests/support/seo-content.test.ts b/tests/support/seo-content.test.ts index 56897189..5fd9532f 100644 --- a/tests/support/seo-content.test.ts +++ b/tests/support/seo-content.test.ts @@ -54,7 +54,13 @@ describe('SEO content fixtures', () => { name: 'Gustavo Ocanto', url: 'https://gocanto.sh', }); - expect(indexHtml).toContain('"url": "https://gocanto.sh"'); + + const jsonLdMatch = indexHtml.match(/]*type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/); + expect(jsonLdMatch?.[1]).toBeDefined(); + + const indexJsonLd = JSON.parse(jsonLdMatch?.[1] ?? '[]') as Array>; + const indexOrganization = indexJsonLd.find((entry) => entry['@type'] === 'Organization'); + expect(indexOrganization?.founder).toEqual(ORGANIZATION_JSON_LD.founder); }); }); From 56bb7038b82b0718964f7fa5344be3fbc1254f38 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Sat, 18 Jul 2026 17:34:21 +0800 Subject: [PATCH 4/4] Derive engagement accessibility labels --- src/pages/WorkWithUsPage.vue | 4 +++- tests/pages/WorkWithUsPage.test.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/WorkWithUsPage.vue b/src/pages/WorkWithUsPage.vue index 27c19e45..fedd6a53 100644 --- a/src/pages/WorkWithUsPage.vue +++ b/src/pages/WorkWithUsPage.vue @@ -51,7 +51,9 @@ {{ item }}

You leave with: {{ engagement.outcome }}

- {{ engagement.cta.label }} + {{ + engagement.cta.label + }}
diff --git a/tests/pages/WorkWithUsPage.test.ts b/tests/pages/WorkWithUsPage.test.ts index 91d726c3..280e3e3e 100644 --- a/tests/pages/WorkWithUsPage.test.ts +++ b/tests/pages/WorkWithUsPage.test.ts @@ -30,7 +30,7 @@ describe('WorkWithUsPage', () => { expect(moreInfoLinks).toHaveLength(workWithUsPageContent.engagements.length); moreInfoLinks.forEach((link, index) => { expect(link.attributes('to')).toBe('/contact'); - expect(link.attributes('aria-label')).toBe(`More info about ${workWithUsPageContent.engagements[index]?.label}`); + expect(link.attributes('aria-label')).toBe(`${workWithUsPageContent.engagements[index]?.cta.label} about ${workWithUsPageContent.engagements[index]?.label}`); }); expect(wrapper.text()).not.toMatch(/(?:[$€£]\s*\d[\d,.]*(?:[km])?|\b\d[\d,.]*(?:[km])?\s*(?:USD|EUR|GBP)\b)/i); });