The site now presents one freelance developer on one page: hero, introduction, four typical cases (developer for a product, make an app reliable, automate a process, a website) with what you get, how it runs and what it costs, a process automation section, technology and project fit, how I work, two live references, questions and contact. - Hero and closing headline are drawn from running binary digits. - Each case explains its four steps with an animated drawing; the automation section shows example workflows with work travelling between systems; a pipeline run shows what a test catches before a release. - Contact is one email address, kept in parts and assembled in the browser so it never appears in the page source (src/domain/email.ts, spec first). - Imprint and privacy pages added. - The old site (blog, case studies, contact form, fixed-price pages, video rendering and related scripts) is removed. Its URLs redirect to the homepage. - The deploy smoke test checks the legal pages and the redirects instead of the removed case study. - Dev scripts use docker compose v2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
323 lines
7.8 KiB
TypeScript
323 lines
7.8 KiB
TypeScript
/**
|
|
* Example workflows for the automation section. They are illustrations of typical
|
|
* cases, not descriptions of specific client projects.
|
|
*/
|
|
|
|
export type NodeKind = "trigger" | "rule" | "ai" | "action" | "human";
|
|
|
|
export type FlowNode = {
|
|
readonly id: string;
|
|
readonly kind: NodeKind;
|
|
/** Position of the node centre on the wide 920 x 400 canvas. */
|
|
readonly x: number;
|
|
readonly y: number;
|
|
/** Position of the node centre on the tall 400 x 540 canvas for phones. */
|
|
readonly nx: number;
|
|
readonly ny: number;
|
|
readonly system: string;
|
|
readonly label: string;
|
|
/** What appears in the log when a run reaches this node. */
|
|
readonly log: string;
|
|
};
|
|
|
|
/** One step of a run: the connections travelled at the same time. */
|
|
export type FlowStep = readonly (readonly [from: string, to: string])[];
|
|
|
|
export type Workflow = {
|
|
readonly id: string;
|
|
readonly name: string;
|
|
readonly summary: string;
|
|
readonly byHand: string;
|
|
readonly automated: string;
|
|
readonly nodes: readonly FlowNode[];
|
|
/** Every connection that exists, for drawing. */
|
|
readonly edges: readonly (readonly [from: string, to: string])[];
|
|
/** The runs that play in turn. Each run is a list of steps. */
|
|
readonly runs: readonly (readonly FlowStep[])[];
|
|
};
|
|
|
|
export const WORKFLOWS: readonly Workflow[] = [
|
|
{
|
|
id: "order",
|
|
name: "Order to invoice",
|
|
summary:
|
|
"A new order arrives in the shop. The invoice, the customer record and the confirmation follow without anyone typing.",
|
|
byHand:
|
|
"Someone opens the order, writes the invoice, updates the customer record and sends an email.",
|
|
automated:
|
|
"The order triggers everything. People only see the finished result.",
|
|
nodes: [
|
|
{
|
|
id: "shop",
|
|
kind: "trigger",
|
|
x: 110,
|
|
y: 200,
|
|
nx: 200,
|
|
ny: 60,
|
|
system: "Shop",
|
|
label: "New order",
|
|
log: "order received",
|
|
},
|
|
{
|
|
id: "stock",
|
|
kind: "rule",
|
|
x: 340,
|
|
y: 200,
|
|
nx: 200,
|
|
ny: 190,
|
|
system: "Rule",
|
|
label: "In stock?",
|
|
log: "stock checked: available",
|
|
},
|
|
{
|
|
id: "invoice",
|
|
kind: "action",
|
|
x: 580,
|
|
y: 110,
|
|
nx: 105,
|
|
ny: 330,
|
|
system: "Accounting",
|
|
label: "Create invoice",
|
|
log: "invoice created",
|
|
},
|
|
{
|
|
id: "crm",
|
|
kind: "action",
|
|
x: 580,
|
|
y: 290,
|
|
nx: 295,
|
|
ny: 330,
|
|
system: "CRM",
|
|
label: "Update customer",
|
|
log: "customer record updated",
|
|
},
|
|
{
|
|
id: "mail",
|
|
kind: "action",
|
|
x: 810,
|
|
y: 200,
|
|
nx: 200,
|
|
ny: 470,
|
|
system: "Email",
|
|
label: "Send confirmation",
|
|
log: "confirmation sent",
|
|
},
|
|
],
|
|
edges: [
|
|
["shop", "stock"],
|
|
["stock", "invoice"],
|
|
["stock", "crm"],
|
|
["invoice", "mail"],
|
|
["crm", "mail"],
|
|
],
|
|
runs: [
|
|
[
|
|
[["shop", "stock"]],
|
|
[
|
|
["stock", "invoice"],
|
|
["stock", "crm"],
|
|
],
|
|
[
|
|
["invoice", "mail"],
|
|
["crm", "mail"],
|
|
],
|
|
],
|
|
],
|
|
},
|
|
{
|
|
id: "documents",
|
|
name: "Incoming documents",
|
|
summary:
|
|
"Invoices arrive as PDF by email. An AI step reads them, a rule checks the result, and a person only sees the unclear ones.",
|
|
byHand:
|
|
"Someone opens every PDF, reads supplier, amount and date, and types them into the accounting system.",
|
|
automated:
|
|
"Clear cases are booked directly. Unclear ones go to a person with the document attached.",
|
|
nodes: [
|
|
{
|
|
id: "inbox",
|
|
kind: "trigger",
|
|
x: 110,
|
|
y: 200,
|
|
nx: 200,
|
|
ny: 60,
|
|
system: "Inbox",
|
|
label: "Email with PDF",
|
|
log: "document received",
|
|
},
|
|
{
|
|
id: "read",
|
|
kind: "ai",
|
|
x: 340,
|
|
y: 200,
|
|
nx: 200,
|
|
ny: 190,
|
|
system: "AI step",
|
|
label: "Read the document",
|
|
log: "supplier, amount and date extracted",
|
|
},
|
|
{
|
|
id: "check",
|
|
kind: "rule",
|
|
x: 570,
|
|
y: 200,
|
|
nx: 200,
|
|
ny: 320,
|
|
system: "Rule",
|
|
label: "Plausible?",
|
|
log: "values checked",
|
|
},
|
|
{
|
|
id: "book",
|
|
kind: "action",
|
|
x: 810,
|
|
y: 110,
|
|
nx: 105,
|
|
ny: 460,
|
|
system: "Accounting",
|
|
label: "Book it",
|
|
log: "booked automatically",
|
|
},
|
|
{
|
|
id: "ask",
|
|
kind: "human",
|
|
x: 810,
|
|
y: 290,
|
|
nx: 295,
|
|
ny: 460,
|
|
system: "A person",
|
|
label: "Review and decide",
|
|
log: "unclear: sent to a person for review",
|
|
},
|
|
],
|
|
edges: [
|
|
["inbox", "read"],
|
|
["read", "check"],
|
|
["check", "book"],
|
|
["check", "ask"],
|
|
],
|
|
runs: [
|
|
[[["inbox", "read"]], [["read", "check"]], [["check", "book"]]],
|
|
[[["inbox", "read"]], [["read", "check"]], [["check", "book"]]],
|
|
[[["inbox", "read"]], [["read", "check"]], [["check", "ask"]]],
|
|
],
|
|
},
|
|
{
|
|
id: "lead",
|
|
name: "Enquiry to follow-up",
|
|
summary:
|
|
"Someone fills in the contact form. The contact is created, the right person is told, and a reminder makes sure nobody forgets.",
|
|
byHand:
|
|
"Someone copies the enquiry into the CRM, forwards it by email and hopes the follow-up is not forgotten.",
|
|
automated: "The enquiry is filed, assigned and followed up on schedule.",
|
|
nodes: [
|
|
{
|
|
id: "form",
|
|
kind: "trigger",
|
|
x: 110,
|
|
y: 200,
|
|
nx: 200,
|
|
ny: 60,
|
|
system: "Website",
|
|
label: "Contact form",
|
|
log: "enquiry received",
|
|
},
|
|
{
|
|
id: "contact",
|
|
kind: "action",
|
|
x: 340,
|
|
y: 200,
|
|
nx: 200,
|
|
ny: 190,
|
|
system: "CRM",
|
|
label: "Create contact",
|
|
log: "contact created",
|
|
},
|
|
{
|
|
id: "notify",
|
|
kind: "action",
|
|
x: 580,
|
|
y: 110,
|
|
nx: 105,
|
|
ny: 330,
|
|
system: "Team chat",
|
|
label: "Notify sales",
|
|
log: "sales notified",
|
|
},
|
|
{
|
|
id: "wait",
|
|
kind: "rule",
|
|
x: 580,
|
|
y: 290,
|
|
nx: 295,
|
|
ny: 330,
|
|
system: "Rule",
|
|
label: "No reply in 3 days?",
|
|
log: "follow-up scheduled",
|
|
},
|
|
{
|
|
id: "remind",
|
|
kind: "action",
|
|
x: 810,
|
|
y: 290,
|
|
nx: 295,
|
|
ny: 470,
|
|
system: "Tasks",
|
|
label: "Create reminder",
|
|
log: "reminder created",
|
|
},
|
|
],
|
|
edges: [
|
|
["form", "contact"],
|
|
["contact", "notify"],
|
|
["contact", "wait"],
|
|
["wait", "remind"],
|
|
],
|
|
runs: [
|
|
[
|
|
[["form", "contact"]],
|
|
[
|
|
["contact", "notify"],
|
|
["contact", "wait"],
|
|
],
|
|
[["wait", "remind"]],
|
|
],
|
|
],
|
|
},
|
|
];
|
|
|
|
export const KIND_LABEL: Readonly<Record<NodeKind, string>> = {
|
|
trigger: "Trigger: something happens",
|
|
rule: "Rule: a fixed check",
|
|
ai: "AI step: reads or sorts",
|
|
action: "Action: a system does something",
|
|
human: "Person: decides the unclear cases",
|
|
};
|
|
|
|
export const AUTOMATION_NOTES = [
|
|
{
|
|
title: "Worth automating when",
|
|
items: [
|
|
"The same steps repeat every day or every week",
|
|
"Data is typed from one tool into another",
|
|
"Mistakes happen because the work is monotonous",
|
|
],
|
|
},
|
|
{
|
|
title: "What stays with people",
|
|
items: [
|
|
"Decisions and exceptions",
|
|
"Anything a customer should hear from a person",
|
|
"The final say when the automation is unsure",
|
|
],
|
|
},
|
|
{
|
|
title: "What I need from you",
|
|
items: [
|
|
"Access to the tools and their interfaces (APIs)",
|
|
"Someone who knows the process as it is done today",
|
|
"Real examples to test with",
|
|
],
|
|
},
|
|
] as const;
|