Skip to content

Applye v0.26.0

Choose a tag to compare

@vitala89 vitala89 released this 29 Jul 13:36

Added

  • Interview Prep can now generate study cards. The stage timeline shipped in v0.16.0 could only log dates and notes by hand - the skills behind it were unwritten stubs, so nothing behind the page actually used AI. Each stage now has a Prep panel: HR/final rounds get behavioral Q&A (STAR+R format) plus comp-negotiation talking points and smart questions to ask the interviewer; technical/system-design rounds get Q&A with working code examples where relevant; behavioral rounds get full STAR+R stories. "Generate 5" becomes "5 more" once cards exist, and re-generating with nothing new since the last batch is a 0-token cache hit.

Fixed

  • AI responses shaped as a JSON array no longer lose their outer brackets. The shared JSON-cleanup helper used by every AI-generation flow only ever looked for {...}, so a response like [{"question":"..."}] had its list brackets stripped down to a bare object and broke on .map(). It now detects whichever bracket type opens first. Affects the new Interview Prep skills; cover letter and CV generation were already object-shaped and are unaffected.

  • Discover can now scan the German job market. The three built-in sources are remote-first and English-speaking, so setting the geo scope to Germany scanned feeds that carry almost no German posting. Discover ships a fourth built-in source, the Bundesagentur für Arbeit job search - the federal employment agency's own public job index, by far the largest in Germany. Like every built-in source it arrives switched off; enable it in the Sources drawer. Full posting texts are fetched only for the jobs that pass your title and location filters, so a scan does not pull hundreds of descriptions you will never see.

  • Company boards on Personio can be added as Discover sources. Personio is the ATS most German small and mid-size employers run their careers page on, and its board feed is public XML. "Add company board" now offers Personio alongside Greenhouse, Lever and Ashby - enter the company's slug (the muster in muster.jobs.personio.de) and its openings join the scan, with the full German posting text including the "Aufgaben" and "Dein Profil" sections.

  • A German city on its own no longer hides a job from a Germany filter. German boards routinely give the location as just "Berlin" or "München", with no country anywhere in the text, so a Germany geo scope silently dropped the very jobs it was set up to find. The largest German cities now count as Germany, in both German and English spelling.

  • German letters now date and close the way DIN 5008 expects. A generated letter carried an ISO date (2026-07-23), which reads as foreign on a German letter, and models kept prefixing the subject with the "Betreff:" label the standard dropped decades ago. In a German letter the date now renders as 23.07.2026 and the label is stripped - both display-only, so a date or subject you typed yourself is left exactly as written. The letter also takes an Enclosures line ("Anlagen: Lebenslauf, Zeugnisse"), printed under the signature when you fill it in and absent when you do not, because a German application travels as a Bewerbungsmappe rather than a single letter.

  • The cover letter can now state your start date and salary expectation. A German posting nearly always asks for a frühestmöglicher Eintrittstermin and a Gehaltsvorstellung, and a letter that answers neither is routinely filtered out before anyone reads it. The letter editor has an Availability and salary card - earliest start date, salary expectation, notice period, all free text, so "ab sofort" and "01.10.2026" are equally valid answers. What you write is worked into the closing paragraph in your own words, never converted or reworded. Leave a field empty and the letter says nothing about it: no "salary negotiable", no invented availability. The answers travel with the letter, so tailoring it for the next job keeps them.

  • One reusable applicant photo lives on the profile. A CV written for the German market conventionally carries a photo, but that photo lived inside a single document, so every new application meant uploading and re-cropping the same headshot. The profile now has a Photo section that reuses the CV editor's crop tool, storing the image already cropped to the CV frame; it saves the moment the crop is confirmed. The image stays in the local database, is never uploaded, and is not sent with any AI call.

  • The tailor wizard raises the German photo convention at the moment it matters. Setting a CV's market to Germany now shows a short explainer with a yes/no choice - a photo is expected there and unusual (sometimes discouraged) elsewhere. "Yes" writes the profile photo into the linked CV; with no photo saved yet, it leads to the profile's Photo section instead. Asked only for Germany, and only once per visit to a job.

  • Target roles collapses like every other profile section. It stayed permanently expanded - five role cards, each with a free-text box - pushing the rest of the page off screen.

  • The apply wizard can be cancelled. The only way out was Back, which stepped to the job summary and left the tailored passes and the generated draft CV and cover letter behind - a user who decided partway through that a job was not worth applying to had no way to say so. The footer now has a Cancel that confirms first, names what is lost, and then deletes the draft documents and the saved wizard progress. Documents already exported or filed keep their place in the library, and the job stays in My Jobs.

  • The sidebar collapses to an icon rail. 240px is a lot to hold permanently for navigation you already know, especially on the Job Tracker and the CV preview where horizontal room is what the content is short of. A control by the logo drops the labels and keeps the icons, and the choice is remembered per machine.

  • The CV live-style panel collapses sideways. Its fixed 340px is exactly the width the preview wants when you are reading the CV rather than restyling it; it now folds to a tab and hands that width to the paper.

Fixed

  • The German tracker report is now German throughout. The Eigenbemühungen sheet is a German-office document, but only its fixed headings followed the chosen report format - the column headers ("Company", "Role", "Status") and the period ("Last 3 months") followed the app's UI language, so a German report handed to the Agentur für Arbeit arrived half in English. Every string printed on the sheet, including the CSV and plain-text exports, now follows the report format; the export dialog around it stays in the UI language.

  • My Jobs: a job's score no longer disappears after you edit your profile. Scores are cached per profile version, so adding a target role or rewriting the profile Markdown changed the hash and the cached score became unreachable - the whole scoring section vanished and the job looked as though it had never been scored. The most recent score is now kept on screen behind an explicit "your profile changed after this score" notice, with the rescore action next to it.

  • The tailor wizard names the market and the language instead of showing codes. The two pickers read "DE / US / UK / GENERIC" and "EN / DE / RU", which hid the very distinction they draw: the first picks the market a CV is written for, the second the language it is written in. They now show "Germany"/"United States" and the same language endonyms Settings uses.

  • The Job Tracker column drawer dropped its misleading LOCKED tag. On a visibility toggle, "LOCKED" read as "this column cannot be shown", when it meant "this value is read-only, it comes from the job posting" - a fact that still belongs on the grid cell, where the tooltip already states it. The redundant pin icon went with it (the pinned column's toggle is already disabled).

  • Release hygiene: the workspace lints clean again. nx run-many -t lint failed with 51 errors and cargo clippy -- -D warnings failed with 10, so neither gate could be trusted before a release. Both are green now. On the Rust side: the dead files.rs placeholder (an empty Files struct with only Phase-1 TODO comments, never constructed) was deleted, two manual splitn(2, ':') parses became split_once, a doc comment that started a line with > was reflowed, and the two lints that are wrong for their context - PhotoPlacement's variant prefix (the names are the serialized wire format) and the macOS print FFI's argument count - carry an #[allow] with the reason. On the Angular side: the five form labels in the interview-stage dialog are now bound to their inputs with for/id, the two column-manager toggles in the Job Tracker became real <button role="switch"> controls (previously click-only <span>s inside a <label> that wrapped no control, so they were unreachable by keyboard), and the remaining click handlers - page-shell click-away, bubble stoppers, and modal backdrops - are annotated as deliberately keyboard-inert. x != null stays allowed in templates via an explicit eqeqeq option instead of being flagged at each site.

  • Discover: target-role matching no longer fires on a generic word like "engineer". An archetype such as "Senior Angular Engineer" was matched to a job when any of its name words appeared, so every engineering opening ("Value Engineer", "Staff System Engineer", "Software Engineer - SAP ABAP") was flagged as a Primary-role match on the shared word "engineer". A match now has to be anchored by a distinctive word (angular, frontend, react, ...); seniority and bare role-family words (senior, engineer, developer, ...) still add coverage but can no longer trigger a match on their own.

  • Discover: the archetype-fit badge no longer disappears on row hover or stretch across the job-detail header. The badge filled with --surface-sunken, which in the light theme is the exact same colour as the row's hover background, so it dissolved into the row; badges now sit on --surface-1 with a 1px border (Primary tier keeps the accent tint), holding their shape on any background in both themes. In the job-detail hero the same badge was stretched to the full row width by the column layout; it now hugs its text.

  • Discover: the filter toolbar reads as one finished bar and pins flush when scrolled. The query input, the source/type/location filters, the New/All toggle, and the Sources/Clear actions were three different heights and border weights, giving the sticky bar a ragged, unfinished edge; every control now shares the input's baseline (30px, subtle border, one radius). The bar also pinned 32px below the viewport top (the scroll container's padding), leaving a strip where feed rows scrolled through above it; it now sticks flush against the top with no gap.

  • My Jobs: an applied job can no longer be reopened for editing. Once a job leaves Saved the pasted job description is frozen (the application is already out the door), so the job detail now shows only Delete - the Edit affordance and its "reopen an in-progress application" confirm dialog were removed. Saved jobs are unaffected (their description stays editable inline).

  • Discover: profile archetypes were serialized as objects but read as [object Object], collapsing all target-role matching to a single junk keyword. Matching now parses the real archetype list.

  • CV experience dates no longer render as a bare "Present" with a leading dash. When a role had no start date the preview showed " - Present"; the date separator is now hidden in read/print modes when the start date is empty (it stays in the editor so the empty field is still clickable to fill).

  • Cover-letter signatures no longer append the phone number to the name. The cover-letter-generate skill states the signature is the sender's name only, but the model did not obey reliably and kept leaking a phone number into the sign-off. Every generated/regenerated signature is now run through a deterministic sanitiser that strips any phone, email, or URL the model appended, leaving just the name - the prompt rule is best-effort, this is the guarantee.

  • Document Generate / Regenerate buttons now show a visible loading indicator. The bouncing three-dot spinner was styled only when nested inside an .ai-thinking badge, so inside the buttons its dots collapsed to zero size and only the disabled state showed. The indicator is now self-contained and inherits the button's text colour, so a regenerate in progress is clearly animated.

  • Review CV / Review letter buttons are disabled while a draft regenerates. They stayed clickable during regeneration and could open a document mid-rebuild; they are now disabled until generation finishes.

  • No more page "blink" when the apply wizard opens on a route change. Opening a job from the floating resume button or returning from the document editor briefly flashed the job-detail view before the wizard/tailor view replaced it, because the wizard-open decision ran after an async job load. That decision is now made synchronously before any await, so the correct view paints first.

  • CV import recovers more mangled ligatures. The ft ligature that some PDFs export as a bare + glued to a word (e.g. "AmcomSo+" -> "AmcomSoft", "Microso+" -> "Microsoft") is now repaired, with the existing C++/ES6+ never-repair guards kept. Date-capture guidance was also tightened so dates present in the source are not dropped.

  • My Jobs "Updated score" sections now have consistent padding. The Dimensions and "Before you submit" blocks were unpadded while the surrounding cards were boxed; every section is now a uniformly padded card.

Added

  • Discover: target-role (archetype) fit badges. Each job in the feed and detail now shows the strongest matching archetype tier (Primary / Secondary / Adjacent); the For-you section is ordered by tier, and tier feeds the deterministic 0-token score. sellWhen acts as a light tie-break signal.
  • Missing-date prompt before generating a tailored CV. When the AI cannot date an experience or education entry, a blocking dialog asks the user to fill it in (per entry, skippable) instead of shipping a CV with an open-ended "Present". Answers can optionally be saved back to the profile.
  • Structured, multi-entry education in the profile. The profile's single education text field is now a repeatable list: add any number of degrees, courses, or certificates, each with its own institution and start/end year (end left blank = ongoing). Stored back into the profile markdown, backward compatible with existing free-text education.
  • Gap-fill before generating a cover letter. Cover-letter generation now runs the same JD-vs-profile gap analysis the CV flow uses, asking the user for anything the posting wants that the profile lacks before the letter is written. Skipped when a CV was already generated for the job (that step just asked), so the user is never prompted twice.
  • Profile editor: structured Experience (role, company, dates, bullets), chip-style Skills, and Language + level editors in Form mode, each in a collapsible section.
  • Profile editor: AI "Parse text" in Raw Markdown mode - turns free text into structured fields via a preview-then-apply flow (new profile-import skill).
  • Profile editor: compensation target (min / max / currency / period) in Form mode.
  • Discovery and job detail: a salary-fit badge (Above / In range / Below target, or "Salary not stated") comparing a job's advertised salary - detected from the job description - to your compensation target. Recognizes yearly and monthly pay; never shows a misleading badge.

Website (applye.dev)

  • Source links honour a single "not public yet" switch. The repository is private while the pre-release audit runs, so every GitHub link on the site would 404. SOURCE_PUBLIC in site.ts now drives a SourceLink component that renders a real link when the repo is public and an honest "coming soon" pill when it is not; nav, footer, landing, blog, press, privacy, sustain, manifesto, methodology, changelog, and docs all go through it. Changelog release headings stop linking to compare views while the repo is private.
  • Consent-gated Google Analytics. GA4 is injected only after an explicit opt-in, with anonymize_ip on and Google Signals and ad personalisation off. Nothing is loaded, and no cookie is written, before consent - the decision itself lives in localStorage. A placeholder measurement ID counts as unconfigured, so no build ever contacts a junk GA property. A consent bar asks once; the new /cookies page documents exactly what is collected and lets the visitor change or reset the choice. The privacy page now states the app-versus-website distinction plainly.
  • Per-route SEO. Every route declares its own meta description, and a SeoService rewrites description, canonical, Open Graph, and Twitter tags on navigation. Adds robots.txt, a generated sitemap.xml (34 URLs, npm run web:sitemap), static SoftwareApplication JSON-LD, and a 1200x630 Open Graph card generated from an SVG source with macOS built-ins (npm run web:og). Tests fail if a route lacks a description or drifts from the sitemap manifest.
  • Documentation brought level with the shipped app. New guides for the Dashboard and the Documents library (CV import and generation, the section editor with per-section style overrides and templates, photo and font ATS warnings, structured cover letters, DOCX vs PDF). The profile guide now covers the structured form editor, AI "Parse text" import, target-role archetypes and their fit tiers, and the compensation target; the Discover guide explains the archetype-fit and salary badges and the For-you ordering. New reference pages: "Your data & backup" (per-OS paths, moving machines, deleting everything) and "Troubleshooting & FAQ". docs/product/MEDIA_SHOTLIST.md lists all 27 screenshot and video slots with capture rules and file names.
  • One icon system. The landing page rendered the same shield glyph for all five principles; icons are now a single stroke set (24x24, 1.7 stroke, currentColor) shared by the principles strip, the theme toggle, and the docs chevron.
  • Every page prerendered to static HTML. The site was client-rendered, so crawlers that do not run JavaScript saw only the default title and description - a shared link to a deep docs page previewed identically to the home page. All 39 routes are now prerendered with their own tags; the Cloudflare SPA-fallback rewrite is gone (it would have hidden those files) and a static 404 page answers unknown paths with a real 404.
  • Switching docs pages now lands at the top of the new page. The site is one document scroll and the router kept the old offset, so clicking the next section while halfway down a page dropped the reader into the middle of the next one, with nothing on screen indicating anything had changed. New navigations go to the top; back and forward still restore where you were. The global scroll-behavior: smooth was part of the same problem - it animated that jump, so the page appeared to slide away by itself - so smooth scrolling now belongs only to the table-of-contents anchors, honouring prefers-reduced-motion and falling back to an instant jump on engines that accept smooth and animate nothing.
  • Two claims on the site were out of date. "Not a job-board scraper - you paste roles you found" predated Discover, which does fetch openings; it now says what is actually true and is the more interesting claim anyway: not a scraper of closed boards, because Discover reads public APIs and feeds that are published for exactly this purpose, and nobody is told what you searched for. And the docs still offered a DOCX export that has been removed from the app - export is the WYSIWYG PDF only, while importing a DOCX CV still works. Both corrected across all six locales.
  • Repositioned from "built for the German market" to a global product. The old framing filtered out most of the audience in the first five seconds, while the DACH capabilities are a genuine differentiator worth keeping. The landing section is now "Local rules, handled" - a job search is local even when the job is remote - covering documents in six languages, per-market CV conventions, visa and Blue Card awareness, and GDPR by architecture, with Germany named as the deepest example rather than the frame. The FAQ answers "Does it work outside Germany?" instead of "Why the German market?", /docs/german became /docs/local-markets with an "everywhere" section plus a "Germany, in depth" one, and the comparison table, press kit and hero mock lost their German-specific wording. All six locales updated.
  • The chosen language survives navigation. Picking Russian and then opening the docs used to drop the reader back into an English shell, and the logo always pointed at the English landing page. The choice is now remembered: the shell (nav, footer, consent bar) stays in the reader's language on English-only pages, and the logo returns them to their own landing page. The page's own lang, canonical and hreflang tags still follow the route, so an English docs page is never advertised as German.
  • The landing page in six languages. English at /, plus /de, /es, /pl, /ru, /uk, matching the languages the README ships in. The shell (nav, footer, consent bar) follows the page's locale, a footer switcher links the versions as real crawlable URLs, and hreflang alternates are emitted between the landing pages only - never claimed for pages that exist in English alone. The documentation stays English, and the translated pages say so rather than pretending otherwise. Tests assert every bundle has exactly the English key set, no empty strings, and a real translation of the headline.