The platform gets personal — a homepage that learns as you go, a profile that means something, and an insights layer for authors

The platform gets personal — a homepage that learns as you go, a profile that means something, and an insights layer for authors

personalisierungprofilanalyticsinsightskategorienseodatenschutzalpha
Fiction

The platform gets personal — a homepage that learns as you go, a profile that means something, and an insights layer for authors

An entire wall made of open book pages — a symbol for a platform that turns many stories into a personal selection Photo: Unsplash

The last few weeks had one clear, big center of gravity: a 42-story flagship catalog turned into a real library with thousands of public-domain classics. That was infrastructure — a lot of inventory, against which the app finally feels full from the very first moment. The last good week and a half revolved around the other half of the same idea: not more stories, but the right stories for the right reader. In other words — OutaStory got personal.

That sounds like a marketing line, but it's meant very concretely and touches five layers that were built during this time and that interlock with each other: a homepage that learns as you go; a profile that actually means something (birthday, address, favorite categories, freely chosen interests); an insights layer for authors that shows who reads their stories and how; a wider genre tree with fresh sample stories so every corner is stocked; and three rewritten about-us pages plus a privacy inventory that keeps all of it transparent. In order.

A homepage that learns as you go — built in three layers

Until now, the OutaStory homepage looked the same for everyone: a promo banner up top, then a fixed sequence of discovery rows (recommendations, trending, new releases, top-rated, category rows, foreign-language stories). A good, but static, arrangement. As of this week, the middle discovery region is personalized — in three cleanly separated build-out stages, each with its own pull request and its own review cycle.

Stage 1 — the engine. At the core sits a PersonalizedHomeBuilder that assembles an "interest profile" per visitor: which categories, which authors, and which story lengths someone prefers. From that, a RailGenerators module generates a series of recommendation rails — "More from Fantasy", "Trending in Science Fiction", "Because you like short stories", "More from Author X". The first rails are populated server-side immediately; the lower ones are only loaded lazily via a signed token as you scroll down, so the initial page render stays lean. All of this sits behind a runtime feature flag (personalized-home-enabled), which defaults to on during the closed testing phase — anyone who wants to can flip it on /developer/feature-flags.

Stage 2 — web rendering and the anonymous path. The second stage brought the actual presentation on the web — a PersonalizedHome component that renders the rails, lazily loads them via an intersection observer, and shows skeleton cards while a row is still loading. Importantly, the personalized feed also works without logging in. Anonymous readers get an idempotent, consistent feed via a stable anonymous ID kept in the browser — no account requirement, no cross-device tracking, just a local key that condenses this session's behavior into recommendations.

Stage 3 — anonymous learning-as-you-go, a fresh-signal overlay, and "readers like you". The third stage is the most interesting one. Here the engine genuinely learns from behavior — from completed reads, from what was just freshly read (a "recent-signal overlay" that layers new signals on top of the nightly computed base profile without waiting for the next batch) — and it can offer a demographic "readers like you" rail: stories that resonate with people of a similar age, gender, and country profile. This demographic rail is deliberately bound to privacy safeguards: it only appears for adult, logged-in users, and only if the comparison group is large enough (a k-anonymity threshold of at least ten readers) — otherwise OutaStory doesn't show the rail at all. No recommendation is ever built on a group that's too small or identifiable.

Something that mattered here and took its own effort: the homepage must never feel empty. For a brand-new visitor without any history, there's nothing to personalize yet — and in that case the page falls back cleanly to the classic discovery arrangement instead of showing an empty personalized region. Personalization is an improvement on top of a solid default, not a replacement that runs into nothing when data is missing.

A profile that means something

An open notebook labeled "Notes", a fountain pen, and reading glasses — a symbol for a profile that reflects the person behind it instead of just a display name Photo: Unsplash

Personalization needs something to learn from. That's why a second big thread ran in parallel: the OutaStory profile grew from a plain display-name-plus-bio into a real profile — in three build-out stages we internally called SP1, SP2, and SP3.

SP1 — age and minor protection under Art. 8. The first stage is the most sensitive one, and the one where we were most careful. OutaStory is a German platform under the GDPR and deliberately serves minors as well. The new age engine (AgeCalculator, MinorAccessDecision, a ParentalConsent flow with an email to parents/guardians) makes it possible to capture a birthday, calculate age from it, and place accounts under 16 behind verifiable parental consent (Art. 8 GDPR, German age threshold of 16). This engine is fully built, but deliberately sits behind a flag that's off by default — it only goes live once our data protection officer has approved the concrete verification method. Better to ship it dark and switch it on cleanly than to go live half-baked.

SP2 — the core profile fields. Birthday, gender (freely selectable, including "diverse" and "prefer not to say"), address (street, postal code, city, country), and favorite categories. All editable via the profile page, all optional in the default configuration. These fields aren't data collection for its own sake — they are exactly the axes along which the demographic recommendation rail described above becomes possible in the first place. Anyone who leaves them blank gets a slightly less specific homepage; anyone who fills them in gets a more precise one.

SP3 — freely chosen interests: occupation, music, hobbies. The third stage brought three curated interest catalogs into the profile: occupational fields (oriented on the European ESCO classification), music genres, and hobby categories — each as a multi-select with localized names. And this is where the circle closes back to the homepage: each of these axes can feed its own recommendation rail — "Popular with jazz fans", "Popular with readers in the same occupation". At most one rail appears per interest type, and the same k-anonymity threshold and the same 18+ gate apply as for the demographic rail. Interests are voluntary; the recommendations that come from them are a pleasant side effect, not a requirement.

Your own avatar — finally uploadable

A small but long-requested building block: the profile picture can now be uploaded yourself. Until now, the avatar came exclusively from the login provider; a "change picture" button existed but did nothing. Now it opens a file dialog, the image is checked server-side for type and size, runs through our existing image content moderation (Azure Content Safety) — a hard block prevents an inappropriate image from ever reaching storage — and lands in a private blob container, from where it's served through a dedicated proxy. The uploaded picture then wins over the provider picture everywhere. Small, but it makes a profile instantly more personal.

An insights layer for authors

A laptop screen full of charts — usage curves, bars, and trend lines — a symbol for the new analytics and insights layer Photo: Unsplash

If readers get a more personal homepage, authors should understand how their stories are being read. That's why a complete analytics and insights layer came together during this stretch — in two halves.

The first half is an analytics area for the platform (/management/analytics), built in five stages: a charting foundation and a dashboard hub (P0), a reading dashboard with time, platform, and category breakdowns (P1a), reading speed in words per minute (P1b, including word-count capture), an "explorer" for drilling down category → story → chapter → page (P1c), and finally audio, geo, engagement, and per-author dashboards (P2–P5). All of it draws on the reading events already captured with minimal data footprint — no additional telemetry, just a better analysis of what we already have.

The second half is the one directly visible to authors: a story insights page (/profile/insights) that shows every logged-in author their own numbers — reading accesses, completed reads, reading time, reading speed, "continue reading" clicks, what time of day people read, on which platform, whether they read or listen, where readers come from, and — wherever the data basis is large enough — a demographic breakdown. Anyone who hasn't published any stories yet, or who hasn't accumulated any reads in the selected time range, doesn't see a cryptic forest of zeros, but a clear message: "There's nothing here yet — as soon as someone reads your stories, the numbers will appear."

Important in both cases: the same k-anonymity discipline as with personalization. A demographic breakdown only appears if there are enough readers behind it that no individual could be identified.

A row of tightly packed books with differently colored spines — a symbol for a more widely branched genre tree with freshly stocked corners Photo: Unsplash

A personalized homepage is only as good as the catalog it draws from. If someone likes "science fiction", but science fiction is only a single node in the category tree, the recommendation stays coarse. That's why the fiction part of the taxonomy was significantly expanded: science fiction grew from four to twenty-one sub-genres, and a dedicated western branch was added. This lets the engine distinguish much more finely — cyberpunk isn't space opera, a cosy mystery isn't a thriller.

A new, empty genre node, however, feels sad. So a whole wave of fresh sample stories came together in parallel — short, two-chapter, AI-tagged texts, each in German and English, per genre: fantasy, romance, mystery, thriller, horror, adventure, western, and 36 science fiction samples. They're small and clearly marked as samples, but they make sure every corner of the tree is stocked from the very start and feels like a place where something is happening — instead of an empty shelf.

About-us, rewritten — and discoverable by humans and machines alike

Three pages got a complete overhaul during this stretch: /about/members, /about/writers, and /about/publishers. Before, these were bullet-point lists with a few promises that didn't quite exist yet. Now they're longer, coherent texts that honestly describe what the platform actually delivers — for readers (reading and listening, a personalized homepage, hundreds of categories, minor protection, apps on every platform), for authors (a writing workshop, AI production tools for covers/descriptions/narration — explicitly not for the writing itself —, revenue sharing, contests, the new insights) and for publishers (a platform, not a competing publisher; rights stay with the authors). With publishers, it was especially important to stay honest: no made-up product, but a clear invitation to talk.

Technically, all three pages got full search-engine and social-media metadata plus structured JSON-LD data (Schema.org AboutPage with breadcrumb navigation), so not just humans but also search engines and AI answer systems understand what the page is about. This fits into a broader SEO/AEO wave: the canonical domain is now www.outastory.com everywhere, content is rendered server-side as crawlable prose, and the story, author, and category pages carry richer structured data. Each page also got its own simple house-style illustration as a preview image.

Privacy, made visible

With all these new data fields — birthday, address, interests, demographic comparison groups — it would have been easy to let the privacy page lag behind. That's exactly what we wanted to avoid. So during this stretch a complete privacy inventory came together: a code-derived document that lists every category of personal data OutaStory collects, stores, and shares — mandatory versus optional, which processors it goes to, how long it's retained, and on what legal basis. Eight concrete decisions were derived from this inventory and translated into binding rules, including:

  • Every non-essential tracker is blocked by default and only fires after matching consent — that includes Sentry's session-replay feature and all ad-network loaders.
  • First-party analytics data carries a 14-month retention window; moderation/abuse IPs a 12-month window — both with documented legal basis and an automatic deletion path.
  • Data minimization by default: IP addresses are stored salted-hashed rather than in plaintext; cross-device detection is limited to coarse signals with a daily rotating salt and is disclosed.
  • Every new personal data field must be maintained in three places at the same time — the machine-readable data-subject-access export, the inventory, and the user-facing privacy notice — so the three never drift apart.

That's not glamorous work, but it's exactly the kind of foundation a platform for readers of every age has to carry. And it fits the project's underlying attitude: get more personal without getting greedy.

Numbers, for the record

  • Pull requests: around 55 in the good week and a half since the last blog post.
  • Main topics: personalized homepage (3 build-out stages), expanded profile (SP1 age/Art. 8, SP2 core fields, SP3 interests), profile-attribute recommendations, avatar upload, analytics area (P0–P5) plus story insights for authors, category expansion plus per-genre sample stories, three rewritten about-us pages plus SEO/AEO wave, privacy inventory plus derived rules.
  • Catalog: the raw-text classics inventory from the previous weeks is now live, the genre tree in the fiction section is noticeably finer-grained, every corner stocked with sample stories.
  • Tests: still in the high three-thousands of green unit tests, coverage gate still in report mode.

What's next

  • Sharpen personalization — more signal sources, better rail ordering, and watching how the feed feels for real testers once enough reading history exists.
  • Turn on minor protection — the Art. 8 engine is built and waits for the DPO to ratify the verification method before the flag goes live in production.
  • Curate sample stories — the AI tags are good, but not perfect; a weekly triage wave sharpens categories and age ratings.
  • App.Shared Razor Pages tests — the last red coverage entry, still ongoing across several weekly waves.
  • Meta app review for Instagram — permission for automatic posting is still pending.

If you spot a recommendation on the homepage while browsing that doesn't fit you at all — or one that's uncannily on point — let me know. That exact feeling, whether the feed "knows you" or misses the mark, is the most valuable signal at this stage, and no dashboard can tell me that.


Comments (0)

No comments yet.


Leave a comment

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please reload the page.