The most voted bug reports and ideas, the link to send one while the
board takes them, and how many want each epic on the roadmap. The mock
API serves a showcase for the catalogue so every section can be seen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a project has a board in TaskStream, its page lists the epics with
their dates and progress, the releases with their notes, the sprint under
way and the tickets meant for newcomers, linking to the board. TaskStream
being slow or down only leaves those sections out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The blog was reachable only from a link in the footer, which is to say
hardly at all. Once there is a post:
- "Blog" joins the header menu, on desktop and in the mobile menu, on
every page.
- The landing shows the three latest posts under "Novedades", with a
link to the rest.
The post card moves into its own component, post-card, shared by the
blog (image beside the text) and the landing (image above).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A technology's name appeared in nine places -- the chips on project,
call, service and member cards and detail pages, the landing's ring
cards, the radar -- and not one was clickable. Now each leads to
`/tecnologias/:slug`, with the logo, what it is, where the collective
places it on the radar and a link to its own site.
Two constraints from the API shaped this, both verified against it:
Its address is the site's own. Technologies carry no `path` and no
`alternates`, and `/slugs` cannot list them -- its type vocabulary is a
closed enum. `routes.ts` opens by stating the API decides every record's
address; this is the first type where that is not true, so `SECTIONS`
gains the one entry and the comment says why. The sitemap composes these
URLs locally for the same reason, and an unreachable technologies call
there is not fatal the way `/slugs` is: it costs a handful of URLs
rather than misrepresenting the site.
There is no detail endpoint. `GET /technologies/{slug}` answers "route
not found" -- the route does not exist. `pireonApi.technology` filters
the collection instead, passing `in_use: false` because the default
returns only technologies attached to a project, which today is none.
An outage and an unknown slug stay distinguishable so the first is not
reported as a 404.
`website` is the only link the payload has: the project's own home page,
where its documentation lives. There is no separate docs field, so the
label says what the link is.
The chip markup was identical in all nine places, so it becomes one
`app-technology-chip` rather than nine anchors. `project-card` and
`radar-page` gain a `locale` input, which is all they lacked to address
it.
Verified: lint, 54 unit tests, build and 11 e2e. Against the mock, the
three addresses answer 200 and an unknown slug 404; the switcher moves
between them; canonical and hreflang are right; the sitemap lists all
three per technology.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A post is a page in the panel's "Blog" category, so it is written,
translated, scheduled and reviewed like any page, and the MCP server
can publish one the same way. Nothing new in the API.
- /blog (/en/blog, /ca/blog): posts newest first, with image, date and
excerpt.
- A post shows its date and leads back to the blog; its structured data
is a BlogPosting.
- /blog/rss.xml?lang=xx: one feed per language, announced in <head>.
- Posts stay out of the header and the footer, which links the blog
once there is any post instead of growing with every one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/colabora (/en/contribute, /ca/col-labora) — the calls' own section, so
it lists what /colabora/{slug} are — brings the open calls, the good
first issues across the collective's repositories, and a five-step
guide from picking a task to having it reviewed. With nothing open and
nothing labelled, the guide and the other ways to help are still there.
The landing's "contribute" section links to it.
Also fixes the landing's issue list, which @pi/ui's `li + li` margin
spaced twice over.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A service now has its address, like a project: icon, tagline, what the
collective does and how, the projects it was delivered on, what the
people it was for said, and the technologies behind it. The landing's
service cards link to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/radar (/en/radar, /ca/radar) shows every technology the collective has
placed, by ring: logo, category, what it is, why it sits there, its
website and how many projects use it. The landing's radar summary links
to it.
It is the first listing — a page this site composes rather than one
record's — so resolveRoute learns about those (LISTINGS), and builds
their addresses and hreflang alternates from the same table.
Logos sit on a light tile in both themes. A brand's mark is not ours to
recolour, and Rust's, which is black, would vanish in dark mode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A call now has its address — description, what the volunteer gets, the
commitment added up, level, closing date, project, technologies — and
the application form right there. The landing's call card leads to it
instead of scrolling to the general form, and shows the level in the
visitor's language rather than the API's `mid` or `any`.
The form is the landing's, with a `callSlug` that hides the kind
selector and posts to the new /api/calls/{slug}/apply handler: the API
refuses a `kind` on an application, which is always an offer of time.
Both handlers share one check, core/submission.ts, now tested on its
own.
A call that is filled or closed keeps its page — a link to it may
still be out there — but shows why there is no form, and where to
write instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API has always served a project's and a member's detail, with
`path` and `alternates` in three languages, and the site had nowhere to
show it. A project now has its page — tagline, status, technologies,
description, technical decisions, what was learned, gallery, client,
dates, lead, team, repositories and open calls — and so does a member:
headline, bio, links, technologies and projects. Cards on the landing
link to both.
Addresses follow the API's rule read backwards (core/routes.ts):
pages at the root, records under a section named in their language,
the default language unprefixed. One catch-all route serves them all.
Not a route per shape: Analog registers each loader as an internal
Nitro route, and Nitro's router cannot hold `/:prefix/:slug` beside
`/:prefix/:section/:slug` — the first became unreachable.
The frame around a record (header, footer, lights, 404/503, title,
description, canonical, hreflang) is one component now, PageShell, and
each type only brings its body, all sharing one layout partial.
Also fixes chips that stepped down one row after the first, on the
landing's cards too: @pi/ui's reset gives every `li + li` a top margin,
right for prose and wrong for a row spaced by `gap`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moving from the landing to a page read as changing sites: a narrow
centred column on a flat background, light headings, no cards. The
page now opens like the landing's hero — the same two lights, the same
left edge as the logo, a bold title with the excerpt as its lead — and
the body's headings are bold like every section title on the landing.
The lights move into a shared mixin (ui/_lights.scss) so the hero and
the pages cannot drift apart. On a page they sit on the whole <main>
with `overflow: clip`, so they fade out on their own instead of ending
in a hard edge right where the text starts, and `clip` rather than
`hidden` keeps the sticky side column working.
A side column keeps a page from being a dead end: "keep reading" cards
for the other pages, its own category first, and a card to get in
touch, both on the landing's card surface.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The landing was one address. It now also answers on the pages the panel
publishes -- "Sobre nosotros" and the like -- at the addresses the API
itself chooses: `path` and `alternates` put the default language
unprefixed and every other one under its code, so `/sobre-nosotros`,
`/en/about-us`, `/ca/qui-som` are the routes. `core/api.ts#contentRoute`
is the single rule for that shape, and `/es/…` is deliberately not one
of them, so a page has one address rather than two.
Two routes share one load function, because Analog wants a `.server.ts`
beside each `.page.ts`: `[slug]` for the default language, and
`[locale]/[slug]` for the rest.
Whether a slug exists is the API's answer, not something the route table
knows, so the 404 middleware lets page-shaped addresses through and
`ui/content-page.ts` sets the status itself from `ngOnInit` -- the same
`injectResponse()` the landing uses for its 503, and `ngOnInit` because
an input has no value in the constructor. Body HTML comes from the
panel's editor and goes through Angular's sanitiser.
The header shows the featured pages beside the section anchors; the
footer lists them all. On a page the language switcher follows the API's
`alternates`, and a language with no translation leads to the landing in
that language instead of a 404.
Verified: lint, 20 unit tests, build, and 6 e2e -- including a new one
that a page-shaped address the API does not know answers 404 with
`robots: noindex`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drops "Hecho con Angular, Analog y Nx." and the API link beside it,
leaving the copyright line on its own.
`footer.built` goes with it in all three locales. So does `footer.api`,
which was already dead -- declared and translated, never rendered.
The row's flex and space-between existed only to push the credit to the
far end, so they go too.
Verified in es/en/ca: no trace of the credit or the docs link in the
served HTML, and the copyright line renders in each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The label read "Horas por semana" struck through by the field's own
border: 5px above the wrapper instead of 18, straddling the 2px outline.
Angular Material measures an outlined field's floating label once on
init and writes the result to the element as an inline transform. This
field is the only one inside an @if, so it is measured before it has any
geometry and keeps the unfloated offset. @pi/ui already publishes the
right values for every other field and its rule matches this one too --
it simply loses to an inline style, so the fix restates the library's
own numbers with `!important`, which is what outranks one.
It also gains a placeholder, which every other field already had.
The e2e config now derives the dev server's port from BASE_URL instead
of pinning 4200. With `reuseExistingServer`, an unrelated project's dev
server on that port gets adopted silently and all five tests fail with
"element(s) not found" while the page under test was never served --
which is exactly what happened while verifying this.
Verified: all four labels now land at -18 with a 48px box, light and
dark; lint, 15 unit tests, build and 5 e2e green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>