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
80 changes: 53 additions & 27 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,66 @@
# Changelog

Notable changes are recorded here using the categories from
Notable changes use the categories from
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
This file starts with the documentation refresh; earlier release notes have not
been reconstructed. See [release note guidance](CONTRIBUTING.md#documentation-and-releases).
The pending v1 notes below cover changes since `v0.7.1`; earlier release entries
have not been reconstructed. See [release note guidance](CONTRIBUTING.md#documentation-and-releases).

## Unreleased
## [Unreleased]

### Added

- Public Collections directory and detail pages for admin-curated alternatives,
repository lists, and ecosystems, separate from private bookmarks. Includes
Markdown, catalogue-backed members, breadcrumbs, responsive numbered pagination
with clean first-page URLs, catalogue/source logo artwork, writer avatars,
bounded sticky navigation/sharing with active-project highlighting, mobile
layouts, canonical metadata, and sitemap discovery.
- Admin Collections CRUD, publication, project selection and ordering, explicit
account authorship, and revision checks. Hidden/deleted projects are filtered
from public collection surfaces. Apply the new Collections database migration
before deploying matching API/web changes; deployment acceptance remains pending.
- Contributor and security reporting guides.
- Development, architecture, and deployment guides.
- Public blog directory and articles with cover images, writer attribution,
publication/update dates, read time, heading permalinks, table of contents,
social images, and sitemap discovery. Admins can create, edit, publish, and
delete posts with rich Markdown authoring, cover uploads, and revision checks.
- Public About page at `/about` and admin editor at `/admin/about`. Title,
description, and Markdown can be previewed and published immediately, with
stale-edit protection and the shared public Markdown renderer.
- Sponsored campaign management with scheduling, artwork, tier eligibility,
weighted selection, and labeled public banner, listing, sidebar, corner,
and sponsor-grid placements.
- Account-free monthly advertising orders with private emailed management links,
Ko-fi payment links, manual receipt confirmation, material review, renewal,
and a durable notification outbox. Payments and refunds are not automated.
- Collection-based related-project suggestions on project detail pages.

### Changed

- Rebuilt the project README and simplified the PRD and roadmap.
- Consolidated documentation into five guides, with configuration/data import in
development, feature behavior in the PRD, and navigation in the root README.
- Replaced deployment-specific configuration with synthetic examples and removed
production evidence and generated QA artifacts from public documentation.
- Catalogue filters support selecting multiple categories and licenses while
preserving search state in the URL.
- Project and collection directories use 45 display slots, sharing available
space with listing ads and backfilling missing or blocked ads with organic items.
- Project cards reveal fuller descriptions on hover and keyboard focus, with
touch-friendly links to project details.
- Updated oRPC to the 2.0 beta API and refreshed frontend/toolchain dependencies.
- Added React Doctor diagnostics to the development workflow and pull requests.
- Consolidated advertising and ranking reference material into the five main
guides and synchronized the README, PRD, and roadmap with current behavior.

### Fixed

- Prevent project slugs from shadowing site routes across admin writes,
publication, imports, and generated submissions, without changing category slugs.
A read-only preflight blocks migration until legacy route collisions are resolved.
- Refresh collection management after failed deletions and omit public links for
drafts. Keep collection detail pages available while supplemental discovery is
pending or fails, rendering discovery cards independently when they become available.
- Keep the initial server/client catalogue offset consistent during hydration,
then restore tab-specific pagination boundaries without reusing another
offset's query results.
- Preserve application-defined OpenAPI error statuses, including upload errors,
after the oRPC upgrade.
- Prevent overlapping project-card links from blocking touch navigation.
- Release busy states after failed asynchronous actions so forms can be retried.
- Index category ancestry lookups and cancel/remove deleted-category detail
variants together, retaining protection against late responses.
- Replace deprecated oRPC client types and Recharts legend positioning props.

### Upgrade notes

- Deploy matching API and web revisions. Apply the additive blog, advertising,
order, and About migrations with `vp run db:migrate` before deploying them.
The reserved-project-slug preflight must pass, including `/blogs` and `/about`.
- Configure ad email delivery using `RESEND_API_KEY`, `RESEND_FROM_EMAIL`, a
trusted `EMAIL_APP_URL`, and optionally `ADS_CONTACT_EMAIL`. Keep an API
instance running for its advertising outbox loop.
- Review the Ko-fi destinations in `AD_PLANS` for the intended instance. Confirm
receipts manually; opening checkout does not record a payment or extend dates.
- Follow the [deployment guide](docs/deployment.md) for proxy `QUERY` support,
migration/configuration checks, scheduled workers, and instance acceptance.

[Unreleased]: https://github.com/isntboxs/altstack/compare/v0.7.1...HEAD
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ activity; save a shortlist; and submit projects for review.
## Features

- Searchable catalogue with hierarchical categories, tags, license filters,
sorting, and observed GitHub star history.
Recommended ranking, other sort orders, and observed GitHub star history.
- Project pages with Markdown documentation, media, link previews, shareable
SVG badges, and admin-curated featured projects.
- GitHub and email Magic Link sign-in, private bookmarks, submission tracking,
rejection feedback, and resubmission.
- Project representative verification, scoped owner/maintainer editing, and
membership management.
- Public curated collections, editorial blogs, and an admin-editable About page
using the shared public Markdown renderer.
- Monthly advertising with private guest order links, Ko-fi checkout links,
manual payment confirmation, artwork review, and explicit renewal.
- Admin workspace for moderation, reports, taxonomy, users, recorded activity,
and operational observations.

Expand Down Expand Up @@ -76,22 +80,18 @@ and recovery, see [deployment](docs/deployment.md).
The five guides below cover local development, the implementation, operations,
and product direction:

| Guide | Contents |
| ------------------------------------ | --------------------------------------------------------------------- |
| [Development](docs/development.md) | Local setup, configuration, testing, troubleshooting, and data import |
| [Architecture](docs/architecture.md) | Package responsibilities, request/data flow, and integrations |
| [Deployment](docs/deployment.md) | Builds, migrations, scheduled workers, monitoring, and recovery |
| [Product requirements](docs/prd.md) | User needs, feature behavior, permissions, and scope |
| [Roadmap](docs/roadmap.md) | Implemented scope, priorities, and conditional future work |
| Guide | Contents |
| ------------------------------------ | ----------------------------------------------------------------------- |
| [Development](docs/development.md) | Local setup, configuration, testing, troubleshooting, and data import |
| [Architecture](docs/architecture.md) | Request/data flow, ranking formula, ad selection/privacy, and rendering |
| [Deployment](docs/deployment.md) | Builds, migrations, workers, advertising operations, and recovery |
| [Product requirements](docs/prd.md) | User needs, feature behavior, permissions, and scope |
| [Roadmap](docs/roadmap.md) | Implemented scope, priorities, and conditional future work |

See [CONTRIBUTING.md](CONTRIBUTING.md) for bug reports, pull requests, and scoped
validation, [SECURITY.md](SECURITY.md) for vulnerability reporting, and
[CHANGELOG.md](CHANGELOG.md) for notable changes.

All deployment examples use localhost or reserved example domains. Keep real
environment files, credentials, deployment identifiers, and production records
outside public documentation.

## License

A repository license has not been selected yet. No project license is declared.
Expand Down
10 changes: 8 additions & 2 deletions apps/web/src/components/action-confirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export function ActionConfirmation({
onConfirm,
children,
disabled = false,
confirmLabel = 'Confirm',
cancelLabel = 'Cancel',
}: {
open: boolean
title: string
Expand All @@ -32,6 +34,8 @@ export function ActionConfirmation({
onConfirm: () => void
children?: ReactNode
disabled?: boolean
confirmLabel?: string
cancelLabel?: string
}) {
return (
<AlertDialog
Expand All @@ -52,9 +56,11 @@ export function ActionConfirmation({
</Alert>
)}
<AlertDialogFooter>
<AlertDialogCancel disabled={pending}>Cancel</AlertDialogCancel>
<AlertDialogCancel disabled={pending}>
{cancelLabel}
</AlertDialogCancel>
<AlertDialogAction disabled={pending || disabled} onClick={onConfirm}>
{pending ? 'Saving…' : 'Confirm'}
{pending ? 'Saving…' : confirmLabel}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
Expand Down
9 changes: 7 additions & 2 deletions apps/web/src/components/category-combobox-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {
useComboboxAnchor,
} from '@altstack/ui/components/combobox'

import { categoryHelp, categoryLabel } from '#/features/admin-categories/model'
import {
categoryHelp,
categoryLabel,
indexCategories,
} from '#/features/admin-categories/model'

const MAX_CATEGORIES = 3

Expand All @@ -32,14 +36,15 @@ export const CategoryComboboxView = ({
categories: Array<CategoryNode & { directProjectCount?: number }>
}) => {
const anchor = useComboboxAnchor()
const categoriesById = indexCategories(categories)

const nameBySlug = new Map<string, string>(
categories.map((category) => [category.slug, category.name])
)
const labelBySlug = new Map(
categories.map((category) => [
category.slug,
categoryLabel(category, categories),
categoryLabel(category, categoriesById),
])
)
const items = categories
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const exploreLinks = linkOptions([
])

const communityLinks = linkOptions([
{ label: 'About us', to: '/about', search: {} },
{ label: 'Blogs', to: '/blogs', search: {} },
{ label: 'Submit a Project', to: '/submit', search: {} },
{ label: 'Advertise', to: '/advertise', search: {} },
Expand Down
8 changes: 8 additions & 0 deletions apps/web/src/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
IconArrowRight,
IconLicense,
IconMenu2,
IconInfoCircle,
IconTag,
} from '@tabler/icons-react'
import {
Expand Down Expand Up @@ -46,6 +47,7 @@ import { ThemeSwitcher } from '#/components/theme-switcher'
import { UserAvatar } from '#/components/user-avatar'

const resourceLinks = [
{ to: '/about', label: 'About us', icon: IconInfoCircle },
{ to: '/blogs', label: 'Blogs', icon: IconArticle },
{ to: '/tags', label: 'Tags', icon: IconTag },
{ to: '/licenses', label: 'Licenses', icon: IconLicense },
Expand Down Expand Up @@ -121,6 +123,12 @@ export const Header = ({
>
Blogs
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => setOpenNavigation(false)}
render={<Link to="/about" />}
>
About us
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => setOpenNavigation(false)}
render={<Link to="/tags" />}
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/components/main-sidebar-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IconChevronDown,
IconFlag,
IconInbox,
IconInfoCircle,
IconLayoutDashboard,
IconPackage,
IconPlus,
Expand Down Expand Up @@ -85,6 +86,7 @@ const catalog = linkOptions([
{ to: '/admin/ads', icon: IconStar, label: 'Ad campaigns' },
])
const administration = linkOptions([
{ to: '/admin/about', icon: IconInfoCircle, label: 'About us' },
{ to: '/admin/users', icon: IconUsers, label: 'Users' },
{ to: '/admin/activity', icon: IconActivity, label: 'Activity' },
{ to: '/admin/operations', icon: IconServer, label: 'Operations' },
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/workspace-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { SidebarTrigger } from '@altstack/ui/components/sidebar'
import { ThemeSwitcher } from '#/components/theme-switcher'

const labels: Record<string, string> = {
'/admin/about': 'About us',
'/admin': 'Overview',
'/admin/submissions': 'Submissions',
'/admin/ownership': 'Ownership requests',
Expand Down
25 changes: 25 additions & 0 deletions apps/web/src/features/about/article.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { AboutBody } from '@altstack/shared/schemas'

import { PublicContent } from '#/components/public-content'

export function AboutArticle({ page }: { page: AboutBody }) {
return (
<div className="container mx-auto flex w-full max-w-6xl flex-col gap-10 px-4 pt-28 pb-10 lg:px-16">
<header className="flex max-w-3xl flex-col gap-4">
<h1 className="text-3xl font-medium tracking-tight wrap-anywhere sm:text-4xl">
{page.title}
</h1>
<p className="text-lg leading-7 text-pretty wrap-anywhere text-muted-foreground">
{page.description}
</p>
</header>
<div className="w-full max-w-3xl min-w-0">
<PublicContent
slug="about"
content={page.content}
headingMode="article"
/>
</div>
</div>
)
}
Loading
Loading