Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/(floe)/floe/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { source } from '@/lib/source';

export const metadata: Metadata = {
// One address for the page, whichever domain it was reached on.
alternates: pageAlternates('/floe'),
alternates: pageAlternates('/floe', '/floe.md'),
openGraph: { images: '/og/site/floe/image.png' },
title: 'Floe',
description:
Expand Down
2 changes: 1 addition & 1 deletion app/(floe)/floe/roadmap/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { sitePages } from '@/lib/site-pages';

export const metadata: Metadata = {
// One address for the page, whichever domain it was reached on.
alternates: pageAlternates('/floe/roadmap'),
alternates: pageAlternates('/floe/roadmap', '/floe/roadmap.md'),
openGraph: { images: '/og/site/floeRoadmap/image.png' },
title: 'Floe roadmap',
description:
Expand Down
209 changes: 3 additions & 206 deletions app/(home)/built-with/page.tsx
Original file line number Diff line number Diff line change
@@ -1,225 +1,22 @@
import type { Metadata } from 'next';
import Image, { type StaticImageData } from 'next/image';
import Image from 'next/image';
import Link from 'next/link';
import fumadocs from '@/assets/stack/fumadocs.png';
import shieldcn from '@/assets/stack/shieldcn.png';
import { Lead, PageHeader } from '@/components/page-header';
import { PageShell } from '@/components/page-shell';
import { Section } from '@/components/section-label';
import built from '@/lib/built-with.json';
import { made, ours, packages, runsOn, type Tool, thanks, type User } from '@/lib/built-with';
import { pageAlternates } from '@/lib/releases';
import { links, products, repoUrl } from '@/lib/shared';
import { stackMarks } from '@/lib/stack-marks';

export const metadata: Metadata = {
// One address for the page, whichever domain it was reached on.
alternates: pageAlternates('/built-with'),
alternates: pageAlternates('/built-with', '/built-with.md'),
openGraph: { images: '/og/site/home/image.png' },
title: 'Built with',
description:
'What Thaw, Floe and this site are built with, the services the project runs on, the licences everything is under, and who it owes.',
};

/** Which of the three something belongs to. */
type User = 'Thaw' | 'Floe' | 'Site';

interface Tool {
name: string;
use: string;
href: string;
/** A Simple Icons path, or the project's own icon; neither, and its initial stands in. */
mark?: keyof typeof stackMarks;
image?: StaticImageData;
/** Left out for a service, which serves the whole project. */
users?: User[];
}

// The languages, frameworks and runtimes, each once with who uses it: Swift is not listed
// twice for two apps, nor Bun once for Floe and again for the site. A package or a tool
// that only checks the code belongs in the package lists further down, not here.
const made: Tool[] = [
{
name: 'Swift',
use: 'The language both apps are written in.',
href: 'https://www.swift.org',
mark: 'swift',
users: ['Thaw', 'Floe'],
},
{
name: 'SwiftUI',
use: 'Their interfaces.',
href: 'https://developer.apple.com/swiftui/',
mark: 'swift',
users: ['Thaw', 'Floe'],
},
{
name: 'Rust and Cargo',
use: 'Floe’s calculator, built on fend.',
href: 'https://www.rust-lang.org',
mark: 'rust',
users: ['Floe'],
},
{
name: 'Bun',
use: 'Runs Floe’s extensions, and builds this site.',
href: 'https://bun.sh',
mark: 'bun',
users: ['Floe', 'Site'],
},
{
name: 'React',
use: 'What Floe’s extensions draw with, and this site.',
href: 'https://react.dev',
mark: 'react',
users: ['Floe', 'Site'],
},
{
name: 'Next.js',
use: 'The site itself.',
href: 'https://nextjs.org',
mark: 'nextdotjs',
users: ['Site'],
},
{
name: 'Fumadocs',
use: 'The docs, and their search.',
href: 'https://fumadocs.dev',
image: fumadocs,
users: ['Site'],
},
{
name: 'Tailwind CSS',
use: 'Its styling.',
href: 'https://tailwindcss.com',
mark: 'tailwindcss',
users: ['Site'],
},
];

// What the project runs on: the main ones. The checks a workflow runs (code scanning,
// dependency updates, signing) are left to the Verified page, which is about them.
const runsOn: Tool[] = [
{
name: 'GitHub',
use: 'Code, issues, releases and discussions.',
href: links.github,
mark: 'github',
},
{
name: 'Pulumi',
use: 'Keeps the GitHub organisation as code: repositories, teams, rules and labels.',
href: `${links.github}/platform`,
mark: 'pulumi',
},
{
name: 'SonarQube Cloud',
use: 'Code quality and test coverage.',
href: 'https://sonarcloud.io',
mark: 'sonarqubecloud',
},
{
name: 'CodeRabbit',
use: 'Reviews pull requests.',
href: 'https://www.coderabbit.ai',
mark: 'coderabbit',
},
{
name: 'Crowdin',
use: 'Where the translations are done.',
href: links.crowdin,
mark: 'crowdin',
},
{
name: 'Homebrew',
use: 'One of the ways to install.',
href: 'https://brew.sh',
mark: 'homebrew',
},
{
name: 'Vercel',
use: 'Hosts this site, through its open source programme.',
href: 'https://vercel.com',
mark: 'vercel',
},
{
name: 'shieldcn',
use: 'Draws the badges across the organisation’s READMEs.',
href: 'https://www.shieldcn.dev',
image: shieldcn,
},
];

// The licence each of ours is under. Floe's is the one its source files name.
const ours = [
{
name: 'Thaw',
license: 'GPL-3.0',
detail: 'The menu bar manager.',
href: `${repoUrl('thaw')}/blob/${products.thaw.branch}/LICENSE`,
},
{
name: 'Floe',
license: 'AGPL-3.0',
detail: 'The launcher.',
href: `${repoUrl('floe')}/blob/${products.floe.branch}/LICENSE`,
},
];

// What is owed that the lists above do not already say. The first two are the ones Thaw's
// own Acknowledgements name; Floe's follow.
const thanks = [
{
to: 'Ice, by Jordan Baird',
forWhat: 'Thaw has its origins in Ice. Thank you for where it started.',
href: 'https://github.com/jordanbaird/Ice',
},
{
to: 'Barometer, by mackid1993',
forWhat:
'Thaw 3’s live system readings and the way it publishes several menu bar items are adapted from Barometer, under the GNU GPLv3 and with permission.',
href: 'https://github.com/mackid1993/Barometer',
},
// Floe's own, from the "Built from" list in its CREDITS.md.
{
to: 'Droppy Code, by Jordy Spruit',
forWhat:
'Floe’s login shell environment, its process runner, the tools that answer Ask AI, its hang watchdog and the folder watcher behind hot reload come from Droppy Code, each changed for Floe and used with his permission.',
href: 'https://getdroppycode.app',
},
{
to: 'The Nix snowflake, by Simon Frankau and Tim Cuthbertson',
forWhat: 'The Nix logo on the roadmap, used under the CC BY 4.0 licence.',
href: 'https://github.com/NixOS/nixos-artwork/tree/master/logo',
},
{
to: 'Natural Earth',
forWhat: 'The outline of the world on the community page’s map, which is in the public domain.',
href: 'https://www.naturalearthdata.com',
},
];

interface Package {
name: string;
url: string;
license: string | null;
/** Who uses it, and at which version. */
users: Partial<Record<User, string | null>>;
}

// What "Made with" already names is not listed a second time as a package, and type
// definitions are left out: they are part of how the code is checked, not of what ships.
const namedAbove =
/^(next|react|react-dom|tailwindcss|@tailwindcss\/.*|fumadocs-.*|@fumadocs\/.*)$/;
const shown = (list: Package[]) =>
list.filter((item) => !item.name.startsWith('@types/') && !namedAbove.test(item.name));

// The packages in one list each, by what they are written in.
const packages: { title: string; list: Package[] }[] = [
{ title: 'Swift packages', list: built.swift },
{ title: 'Rust crates', list: built.rust },
{ title: 'JavaScript packages', list: shown(built.javascript) },
];

/** Who uses something, as small quiet tags. */
function Users({ users }: { users: User[] }) {
return (
Expand Down
75 changes: 3 additions & 72 deletions app/(home)/community/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,90 +6,21 @@ import { PageShell } from '@/components/page-shell';
import { Section } from '@/components/section-label';
import { WorldMap } from '@/components/world-map';
import community from '@/lib/community.json';
import { contributors, counted, numbers, translatorCount, ways } from '@/lib/community-page';
import { pageAlternates } from '@/lib/releases';
import { compact, docsRoute, links, longDate, repoUrl } from '@/lib/shared';
import { compact, longDate, repoUrl } from '@/lib/shared';
import { sitePages } from '@/lib/site-pages';
import { readDays } from '@/lib/verified';

export const metadata: Metadata = {
// One address for the page, whichever domain it was reached on.
alternates: pageAlternates('/community'),
alternates: pageAlternates('/community', '/community.md'),
openGraph: { images: '/og/site/community/image.png' },
title: 'Community',
description: 'Thaw in numbers, where to find the people behind it, and who has contributed.',
};

const repo = repoUrl('thaw');

// The team is shown on its own, so it is left out of the list of contributors.
const teamLogins = new Set(community.team.map((member) => member.login));
const contributors = community.people.filter((person) => !teamLogins.has(person.login));

// A person who translates into two languages is one person.
const translatorCount = new Set(
community.translators.flatMap((group) =>
group.people.map((person) => ('username' in person && person.username) || person.name),
),
).size;

// The day the numbers were read, and any kept from an earlier day.
const counted = readDays(community.readOn, {
stars: 'Stars',
downloads: 'Downloads',
homebrewYear: 'Homebrew installs',
contributors: 'Contributors',
releases: 'Releases',
discord: 'Discord members',
});

// Every number is fetched when the site is built; one that could not be is left out. Six,
// each said once on the page: the countries are counted beside the map under them, and the
// Discord's members where the page says how to join it.
const numbers = [
{ label: 'GitHub stars', value: community.stars, unit: 'on thaw\u2011app/Thaw' },
// Every fetch of the app or of an update to it: one person on ten releases is ten.
{ label: 'Downloads', value: community.downloads, unit: 'of the app and its updates' },
{ label: 'Homebrew installs', value: community.homebrewYear, unit: 'in the last year' },
{
label: 'Contributors',
value: contributors.length + community.team.length,
unit: 'with commits since January 2026',
},
// English, which Thaw is written in, and every language it has been translated into.
{
label: 'Languages',
value: community.translators.length + 1,
unit: 'English and its translations',
},
{ label: 'Releases', value: community.releases, unit: 'published on GitHub' },
].filter((number) => typeof number.value === 'number');

// The ways in, each to the place it happens. One row, since the footer has the same doors.
const ways = [
{
title: 'Fix something',
detail: 'Where each kind of work happens, and what a pull request needs.',
href: `${docsRoute}/thaw/contribute`,
},
{
title: 'Translate it',
detail: 'Done on Crowdin, with no pull request. This is how to join.',
href: `${docsRoute}/thaw/contribute/translations`,
},
{
title: 'Talk to us',
detail: community.discord
? `${compact.format(community.discord)} people are on the Discord server.`
: 'Questions and conversation, on Discord.',
href: links.discord,
},
{
title: 'Sponsor it',
detail: 'Through GitHub Sponsors, for support and partnerships.',
href: links.sponsors,
},
];

export default function CommunityPage() {
return (
<PageShell>
Expand Down
2 changes: 1 addition & 1 deletion app/(home)/faq/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { thawFaq } from '@/lib/thaw-faq';

export const metadata: Metadata = {
// One address for the page, whichever domain it was reached on.
alternates: pageAlternates('/faq'),
alternates: pageAlternates('/faq', '/faq.md'),
openGraph: { images: '/og/site/faq/image.png' },
title: 'Frequently asked questions',
description:
Expand Down
4 changes: 3 additions & 1 deletion app/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import { homeFaq, thawFaq } from '@/lib/thaw-faq';
const badgeFont = Newsreader({ subsets: ['latin'], weight: ['500'], preload: false });

export const metadata: Metadata = {
// What Thaw is, beside its name, in the tab and in a search result.
title: { absolute: `${products.thaw.name}: the open source menu bar manager for macOS` },
// One address for the page, whichever domain it was reached on.
alternates: pageAlternates('/'),
alternates: pageAlternates('/', '/index.md'),
};

/**
Expand Down
Loading
Loading