openvibers/openvibe.rent Commits

openvibe-shared v4.108.0 (was v4.107.4) (#211)

OP OpenVibers committed 10 days ago · 95bf4da40b · 8 files, +52 −23

* openvibe-shared v4.108.0 (was v4.107.4)

Opened by scripts/pin-bump.js in OpenVibers/OpenVibe.Shared after the v4.108.0 release.

* llms.txt links each page at its Markdown twin, with an ## Optional section (AGT-5)

* llms.txt: the JSON API stays a main section, not Optional (AGT-5 review)

Changes

ddiff --git a/README.md b/README.md
iindex 28baaff..f75d678 100644
---- a/README.md
++++ b/README.md
@@@ -409,6 +409,6 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
<!-- versions:start -->
- openvibe-contracts: v0.222.0
- openvibe-sdk: v0.82.0
--- openvibe-shared: v4.107.4
++- openvibe-shared: v4.108.0
- openvibe-publishing: v1.4.0
<!-- versions:end -->
ddiff --git a/STATUS.json b/STATUS.json
iindex ea0ccd2..39c5c03 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -9,11 +9,11 @@
"domain": "openvibe.rent",
"contracts": "openvibe-contracts v0.222.0",
"sdk": "openvibe-sdk v0.82.0",
-- "shared": "openvibe-shared v4.107.4",
++ "shared": "openvibe-shared v4.108.0",
"packages": [
"openvibe-contracts v0.222.0",
"openvibe-sdk v0.82.0",
-- "openvibe-shared v4.107.4",
++ "openvibe-shared v4.108.0",
"openvibe-publishing v1.4.0"
],
"summary": "OpenVibe.Rent — Find a place, or rent yours out. One place to find and offer things to rent: apartments, rooms, houses, commercial space, parking and storage, and equipment. In this first version every listing is posted by a person on this site: no scraped listings and no partner feeds, no payments, no deposits and no messaging. Search with filters and facets, a listing page with a plain safety note, reports that hide a listing at 3 until staff look, saved searches that count what is new, a 30-day expiry an owner can renew, and a FHFA House Price Index card on the affordable-housing page.",
ddiff --git a/package-lock.json b/package-lock.json
iindex a3c35d8..2b971ad 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -18,7 +18,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",
"pg": "^8.23.0"
},
"devDependencies": {
@@@ -1209,9 +1209,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "4.107.4",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",
-- "integrity": "sha512-tThN0m3KMncEHVcBOvE+ysHjsZTlVIDLG1P+/i2Mulmxfc/AUpebAXhpWWrut50OChZ8o+HeePxsSrPI8mUQOQ==",
++ "version": "4.108.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",
++ "integrity": "sha512-NPzwjxpzGIpFpIm4uw3+VwHfsxbJ2+cD9yuhRzSwolSwqv2kCmwaVtHJ0DA9gKO3K8PCBfIP3UpKzpuSbYEJ+g==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0",
ddiff --git a/package.json b/package.json
iindex 15ed9a3..35740ce 100644
---- a/package.json
++++ b/package.json
@@@ -25,7 +25,7 @@
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.222.0",
"openvibe-publishing": "https://codeload.github.com/OpenVibers/OpenVibe.Publishing/tar.gz/refs/tags/v1.4.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",
"pg": "^8.23.0"
},
"engines": {
ddiff --git a/server/homes/pages.js b/server/homes/pages.js
iindex 2c7cfa9..a07bdef 100644
---- a/server/homes/pages.js
++++ b/server/homes/pages.js
@@@ -254,7 +254,27 @@ ${out.hoa ? html`<tr><th scope="row">HOA</th><td>${money(out.hoa)}</td></tr>` :
r.get('/sitemap.xml', (req, res) => res.type('application/xml').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 }))
.send(`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${['/', '/search', '/records', '/mortgage', ...cityPaths()].map((p) => `<url><loc>${base}${p}</loc></url>`).join('')}</urlset>\n`));
// What llms.txt says; /llms-full.txt adds what each page does (SEO-33: it answered 404).
-- const homesLlms = () => `# OpenVibe.Homes\n\n> Buying a home in the United States: one search opens every for-sale site's own search (Zillow, Realtor.com, Redfin, Homes.com, Trulia, Craigslist), HUD Homestore, Fannie Mae HomePath and foreclosure auctions, with county property records and a mortgage calculator. Listings stay on those sites.\n\n- [Search every site](${base}/search)\n- [Homes by city](${base}/in): the median home value in ${cities.cities.length} US cities (Census ACS), with every for-sale site's search for each\n- [Property records by state](${base}/records)\n- [Mortgage calculator](${base}/mortgage)\n- [Renting: OpenVibe.Rent](${config.baseUrl}/everywhere)\n`;
++ const homesLlms = () => seo.llmsTxt({
++ name: 'OpenVibe.Homes',
++ summary: HOMES_SUMMARY,
++ details: 'Listings stay on the sites this search opens. Nobody pays OpenVibe for the links and we sell no leads.',
++ markdown: true,
++ base,
++ sections: [
++ { title: 'Start here', links: [
++ { title: 'OpenVibe.Homes', url: `${base}/` },
++ { title: 'Search every site', url: `${base}/search` },
++ { title: 'Homes by city', url: `${base}/in`, note: `the median home value in ${cities.cities.length} US cities (Census ACS), with every for-sale site's search for each` },
++ { title: 'Property records by state', url: `${base}/records` },
++ { title: 'Mortgage calculator', url: `${base}/mortgage` },
++ ] },
++ ],
++ optional: [
++ { title: 'Sitemap', url: `${base}/sitemap.xml` },
++ { title: 'Full text for language models', url: `${base}/llms-full.txt` },
++ { title: 'Renting: OpenVibe.Rent', url: `${config.baseUrl}/everywhere`, note: 'one search across the rental sites' },
++ ],
++ });
r.get('/llms.txt', (req, res) => res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(homesLlms()));
r.get('/llms-full.txt', (req, res) => res.type('text/plain').set('Cache-Control', cache.htmlHeaders({ maxAge: 3600 })).send(`${homesLlms()}
## The pages in full
ddiff --git a/server/http/discovery.js b/server/http/discovery.js
iindex dd85cda..6fb4f5a 100644
---- a/server/http/discovery.js
++++ b/server/http/discovery.js
@@@ -94,31 +94,34 @@ function createDiscoveryRoutes(ctx) {
name: SITE_NAME,
summary: DESCRIPTION,
details: 'OpenVibe.Rent is a rental aggregator. Search everywhere takes one place and set of filters and opens the matching search on every major rental site (Craigslist, Facebook Marketplace, Zillow, Apartments.com, Zumper, Rent.com, RentCafe, Apartment List, PadMapper, HotPads, Trulia, Realtor.com, and for rooms, stays, commercial space, parking, storage and cars the sites that cover them); the listings open on those sites, in the person\'s own browser. Rent by city has a page for 885 US cities with the typical (median gross) rent by number of bedrooms from the Census Bureau\'s American Community Survey, and /fair checks any quoted rent against it. The board keeps listings from any site in one list, saved searches send alerts through OpenVibe.Watch, and people post listings here too (a city and a neighbourhood, never a street address). Affordable housing comes from U.S. HUD open data (public domain). There are no payments, no deposits and no messaging on OpenVibe.Rent: never pay before you have seen the place and signed an agreement. Every page is server-rendered and readable without JavaScript.',
++ markdown: true,
++ base: site,
sections: [
{ title: 'Start here', links: [
{ title: 'OpenVibe.Rent', url: abs('/'), note: 'find something to rent, or offer your own' },
{ title: 'Search every rental site', url: abs('/everywhere'), note: 'one place and filters open Craigslist, Facebook Marketplace, Zillow, Apartments.com, Zumper and more at once' },
-- { title: 'Rent by city', url: abs('/in'), note: 'typical rents by size in 885 US cities (Census ACS), with every rental site\'s search for each city' },
-- { title: 'Is this rent fair?', url: abs('/fair'), note: 'a quoted monthly rent against the city\'s median for that size' },
++ { title: 'Rent by city', url: abs('/in'), html: true, note: 'typical rents by size in 885 US cities (Census ACS), with every rental site\'s search for each city' },
++ { title: 'Is this rent fair?', url: abs('/fair'), html: true, note: 'a quoted monthly rent against the city\'s median for that size' },
{ title: 'Affordable housing', url: abs('/housing'), note: 'public housing, LIHTC and HUD-assisted properties, and where to apply, near you' },
{ title: 'Listings', url: abs('/listings'), note: 'search by kind, city, country, price and period' },
{ title: 'Renting safely', url: abs('/safety'), note: 'the rules, including the one about deposits' },
-- { title: `What shipped on ${SITE_NAME}`, url: abs('/updates') },
] },
-- { title: 'Machine-readable', links: [
++ // A JSON API is what an agent came for, not something to skip (AGT-5 review).
++ { title: 'API', links: [
{ title: 'Listings (JSON)', url: abs('/api/v1/listings'), note: 'active listings, newest first, with facets' },
{ title: 'Search every rental site (JSON)', url: abs('/api/v1/everywhere?q=salt+lake+city&kind=apartment'), note: 'the geocoded place and the search link on every rental site' },
{ title: 'Affordable housing (JSON)', url: abs('/api/v1/housing?q=washington'), note: 'the geocoded place and normalised HUD housing results near it' },
-- { title: 'Sitemap', url: abs('/sitemap.xml') },
-- { title: 'Full text for language models', url: abs('/llms-full.txt') },
-- { title: 'Release metadata (JSON)', url: abs('/release.json') },
-- ] },
-- { title: 'Elsewhere', links: [
-- { title: 'OpenVibe.Network', url: 'https://openvibe.network', note: 'accounts, apps and grants' },
-- { title: 'OpenVibe.Watch', url: 'https://openvibe.watch', note: 'where alerts for a saved search will come from later' },
-- { title: 'OpenVibe.Services', url: 'https://openvibe.services', note: 'apps, keys and capability grants' },
] },
],
++ optional: [
++ { title: 'Sitemap', url: abs('/sitemap.xml') },
++ { title: 'Full text for language models', url: abs('/llms-full.txt') },
++ { title: 'Release metadata (JSON)', url: abs('/release.json') },
++ { title: 'OpenVibe.Network', url: 'https://openvibe.network', note: 'accounts, apps and grants' },
++ { title: 'OpenVibe.Watch', url: 'https://openvibe.watch', note: 'where alerts for a saved search will come from later' },
++ { title: 'OpenVibe.Services', url: 'https://openvibe.services', note: 'apps, keys and capability grants' },
++ { title: `What shipped on ${SITE_NAME}`, url: abs('/updates') },
++ ],
}));
});
ddiff --git a/test/discovery.test.js b/test/discovery.test.js
iindex cef2d4f..6fce90e 100644
---- a/test/discovery.test.js
++++ b/test/discovery.test.js
@@@ -51,10 +51,12 @@ const PRIVATE = ['/auth/', '/api/'];
assert.match(r.headers.get('content-type'), /^text\/plain/);
assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);
assert.ok(r.text.startsWith('# OpenVibe.Rent'), 'no title');
-- assert.ok(r.text.includes('(https://openvibe.rent/updates)'), 'no updates link');
++ assert.ok(r.text.includes('(https://openvibe.rent/index.md)'), 'home link points at its Markdown twin');
++ assert.ok(r.text.includes('(https://openvibe.rent/updates.md)'), 'no updates link');
assert.ok(r.text.includes('(https://openvibe.services)'), 'no pointer to the developer platform');
assert.ok(r.text.includes('https://openvibe.rent/sitemap.xml'), 'sitemap not listed');
assert.ok(r.text.includes('(https://openvibe.rent/llms-full.txt)'), 'llms-full.txt not listed');
++ assert.ok(r.text.includes('\n## Optional\n'), 'llms.txt has an Optional section');
for (const p of ['/auth/']) assert.ok(!r.text.includes(`https://openvibe.rent${p}`), `private path in llms.txt: ${p}`);
});
ddiff --git a/test/homes.test.js b/test/homes.test.js
iindex db03178..34cb323 100644
---- a/test/homes.test.js
++++ b/test/homes.test.js
@@@ -115,12 +115,16 @@ const housingFetchImpl = async (url) => (new URL(String(url)).pathname.endsWith(
assert.match((await homes('/robots.txt')).text, /Sitemap: https:\/\/openvibe\.homes\/sitemap\.xml/);
assert.ok((await homes('/sitemap.xml')).text.includes('<loc>https://openvibe.homes/mortgage</loc>'));
// SEO-33: llms.txt, then every page in full.
++ const llms = await homes('/llms.txt');
const full = await homes('/llms-full.txt');
++ assert.strictEqual(llms.status, 200);
++ assert.ok(llms.text.includes('(https://openvibe.homes/index.md)'), 'home link points at its Markdown twin');
++ assert.ok(llms.text.includes('\n## Optional\n'), 'llms.txt has an Optional section');
assert.strictEqual(full.status, 200);
assert.ok(full.text.startsWith('# OpenVibe.Homes'));
assert.match(full.text, /## The pages in full\n\n### Search every site\nURL: https:\/\/openvibe\.homes\/search\n/);
assert.ok(full.text.length > 1000, `${full.text.length} characters`);
-- assert.ok(full.text.startsWith((await homes('/llms.txt')).text), 'llms.txt is unchanged and leads it');
++ assert.ok(full.text.startsWith(llms.text), 'llms.txt is unchanged and leads it');
});
await check('homes by city: the median value (the top code as a range), the mortgage on it, renting there, every site, nearby; in the sitemap', async () => {