0.4.0: candidates, importing a job from a URL, and editing a listing
The board had jobs and no people. It has both now, and the half that was missing brought most of this release with it.
Upgrade from 0.3.0 or earlier. 0.3.0 shipped before all of this, and two of the fixes below are about data being quietly destroyed on the way into the database.
Candidates
/candidates lists the resumes their owners published, and /candidates/<name> is one of them. link visibility is served too and is noindex, which is the whole difference between it and public: reachable, unlisted.
The resumes table has had private | link | public since it was written, with a column comment saying public: listed. Nothing ever listed them. Choosing "public" did nothing at all until now.
A candidate card is built from the parsed resume rather than a profile typed twice: name, headline, location and skills all come out of the document. The directory deliberately does not show contact details, because a page listing a hundred addresses is a mailing list for whoever fetches it once; the profile page does, since that is the page its owner chose to publish.
Import a job from a URL
agenticjobs new https://example.com/jobs/123 --org acme
agenticjobs update https://example.com/jobs/123A page publishing schema.org JobPosting is read from that data, @graph included. A page that does not is read off the page itself and says so, because those two are not the same quality of import.
Every import lands as a draft. Extraction from a page nobody designed for it is guesswork at the edges, and a board whose pitch is that nothing here is scraped cannot publish a scrape unread.
Importing the same URL twice refreshes that listing rather than making a second copy of one job. --slug adopts a listing written by hand into the importer.
The fetch reuses the directory's guarded fetch: localhost and the private ranges are refused, redirects may not leave their origin, the body is capped as it arrives, and there is a timeout.
Edit a listing
agenticjobs edit <slug> job.mdThe board could create a listing, publish it and close it, and could not change a word of it. A typo could only be fixed by closing it and posting again under a new slug, breaking every link to it.
Applications are taken on the board
applyVia: url and applyVia: email are gone, and POST /api/v1/jobs refuses them with the reason rather than silently rewriting. A board where some listings are applicable and some are links is one an agent has to filter, and the listings it filters out are the ones an employer cared enough to cross-post.
Existing rows are converted by migration; the old target is kept in apply_source_url as provenance. SMTP_URL is gone, replaced by RESEND_API_KEY (that landed in 0.3.0 and is repeated here because a 0.2.0 install will meet both changes at once).
Tags and feeds
Every tag on a job or a candidate links to the listings that carry it, on both the list and the detail page. Tags accumulate and narrow: ?tags=javascript,react,node.js means all three.
/feed?tags=react,go jobs, filtered like the page is
/candidates/feed?tags=js,react people, filtered the same way
/feed.rss everything
Job tags could not have been links before this: the whole card was one <a>, and an anchor cannot contain another. The title carries the link now, which also means a screen reader gets one link per destination instead of one enormous link.
Fixes worth naming
- Newlines were being stripped from every document.
clean()flattened control characters, and job descriptions, resume Markdown and cover letters all went through it. Every one of them arrived as a single paragraph with its headings and lists destroyed. If you have resumes or listings saved before this release, their formatting is already gone; re-saving them will not bring it back. - The mobile job page scrolled sideways. One long URL in a code block, an
frgrid track that could not shrink below it, and a document 916px wide at a 375px viewport. - The service worker served a stale stylesheet indefinitely, then served a stale one under a fresh cache name, because
cache.addAllreads through the HTTP cache. Both fixed; the cache name is now a hash of the assets and a test recomputes it. - A board no longer appears in its own directory, guarded on the heartbeat, the CLI and the announce endpoint.
113 tests against a real Postgres.