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
19 changes: 19 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "temporal-learning-dev",
"runtimeExecutable": "yarn",
"runtimeArgs": ["start"],
"port": 3000,
"autoPort": true
},
{
"name": "temporal-learning-serve",
"runtimeExecutable": "yarn",
"runtimeArgs": ["serve"],
"port": 3001,
"autoPort": true
}
]
}
92 changes: 92 additions & 0 deletions bin/check-font-preload-hash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env node

// Aeonik/Poppins weights are preloaded (docusaurus.config.js headTags,
// vercel.json's Link header) by their literal webpack output filename,
// because that's the only way to preload a font before the browser has HTML
// to read a <link> tag or CSS to parse an @font-face from. That filename is
// content-hashed and hardcoded in src/constants/preloadFonts.js — it only
// changes if the underlying font file's bytes change, which is rare, but
// silent drift here means the preload references a file the browser will
// never actually request: wasted bandwidth on a resource nothing uses, and
// a real one that gets no early-fetch benefit.
// This check fails the build if that ever happens, instead of relying on
// someone noticing a "preloaded but not used" warning in devtools.

const fs = require("fs");
const path = require("path");

const CSS_DIR = path.join(process.cwd(), "build", "assets", "css");
const VERCEL_JSON_PATH = path.join(process.cwd(), "vercel.json");
const PRELOAD_FONTS_PATH = path.join(process.cwd(), "src", "constants", "preloadFonts.js");

const FONTS = [
{ key: "AEONIK_REGULAR_FILENAME", prefix: "Aeonik-Regular", ext: "woff" },
{ key: "AEONIK_BOLD_FILENAME", prefix: "Aeonik-Bold", ext: "woff" },
{ key: "AEONIK_LIGHT_FILENAME", prefix: "Aeonik-Light", ext: "woff" },
{ key: "POPPINS_REGULAR_FILENAME", prefix: "Poppins-Regular", ext: "ttf" },
{ key: "POPPINS_MEDIUM_FILENAME", prefix: "Poppins-Medium", ext: "ttf" },
{ key: "POPPINS_SEMIBOLD_FILENAME", prefix: "Poppins-SemiBold", ext: "ttf" },
{ key: "POPPINS_BOLD_FILENAME", prefix: "Poppins-Bold", ext: "ttf" },
];

function findActualFilename(cssContent, prefix, ext) {
const match = cssContent.match(new RegExp(`${prefix}-[0-9a-f]+\\.${ext}`));
return match ? match[0] : null;
}

function main() {
if (!fs.existsSync(CSS_DIR)) {
console.error(`[check-font-preload-hash] ${CSS_DIR} not found. Run \`yarn build\` first.`);
process.exit(1);
}

const cssContent = fs
.readdirSync(CSS_DIR)
.filter((f) => f.endsWith(".css"))
.map((f) => fs.readFileSync(path.join(CSS_DIR, f), "utf8"))
.join("\n");

// eslint-disable-next-line import/no-dynamic-require, global-require
const preloadFonts = require(PRELOAD_FONTS_PATH);
const vercelJson = fs.readFileSync(VERCEL_JSON_PATH, "utf8");

const failures = [];

for (const { key, prefix, ext } of FONTS) {
const actual = findActualFilename(cssContent, prefix, ext);
const expected = preloadFonts[key];

if (!actual) {
failures.push(
`Could not find a "${prefix}-<hash>.${ext}" reference anywhere in ${CSS_DIR}/*.css. Did the @font-face rule move or get removed from src/css/custom.css?`,
);
continue;
}

if (actual !== expected) {
failures.push(
`${prefix}: build now produces "${actual}", but src/constants/preloadFonts.js:${key} says "${expected}".\n`
+ ` Fix: in src/constants/preloadFonts.js, set ${key} to '${actual}'.\n`
+ ` Then in vercel.json, replace "${expected}" with "${actual}" in the Link header value (source: "/(.*)").`,
);
continue;
}

if (!vercelJson.includes(actual)) {
failures.push(
`${prefix}: src/constants/preloadFonts.js:${key} ("${actual}") matches the build, but vercel.json's Link header doesn't reference it.\n`
+ ` Fix: in vercel.json, update the Link header value (source: "/(.*)") to include "</assets/fonts/${actual}>; rel=preload; as=font; type=\\"font/${ext}\\"; crossorigin".`,
);
}
}

if (failures.length > 0) {
console.error(`\n[check-font-preload-hash] ${failures.length} check(s) failed:\n`);
failures.forEach((f) => console.error(` - ${f}\n`));
process.exit(1);
}

console.log("[check-font-preload-hash] OK: preloaded font hashes match the build output.");
}

main();
3 changes: 3 additions & 0 deletions docs/getting_started/_temporal_service.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

The fastest way to get a development version of the Temporal Service running on your local machine is to use [Temporal CLI](https://docs.temporal.io/cli).

Temporal CLI is a tool for interacting with the Temporal Service from the command-line interface. It includes a self-contained distribution of the Temporal Service and [Web UI](https://docs.temporal.io/web-ui) as well which you can use for local development.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1478,14 +1478,19 @@ Let's demonstrate Temporal's durability. We'll show that Workflows continue runn

#### Step 1: Process a New Invoice

1. In Claude Desktop, ask something like: `Process this invoice:
{
"id": "INV-002",
"lines": [
{"item": "Web Development", "amount": 3000.00, "description": "Frontend work"},
{"item": "Design Services", "amount": 2000.00, "description": "UI/UX design"}
]
}`.
1. In Claude Desktop, ask something like:

```text
Process this invoice:
{
"id": "INV-002",
"lines": [
{"item": "Web Development", "amount": 3000.00, "description": "Frontend work"},
{"item": "Design Services", "amount": 2000.00, "description": "UI/UX design"}
]
}
```

2. Click **"Allow"** when prompted to use the tool
3. Claude will show that it's waiting for the tool response

Expand Down
3 changes: 3 additions & 0 deletions docs/tutorials/java/audiobook/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ sidebar_label: Create audiobooks from text with OpenAI and Java
image: /img/temporal-logo-twitter-card.png
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

<img className="banner" src="/img/sdk_banners/banner_java.png" alt="Temporal Java SDK" />

## Introduction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ This will display output similar to the following:

```shell
Progress:
ID  Time   Type   Details 
ID Time Type Details
1 2023-11-08T19:46:20Z WorkflowExecutionStarted {WorkflowType:{Name:BackgroundCheckReplayNonDeterministicWorkflow},
ParentInitiatedEventId:0,
TaskQueue:{Name:backgroundcheck-replay-task-queue-local,
Expand Down
44 changes: 32 additions & 12 deletions docs/tutorials/nexus/sync-nexus-tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ Try the [Nexus Quick Start](https://docs.temporal.io/nexus) for a faster path. C

_The Payments team owns validation and execution (left). The Compliance team owns risk assessment, isolated behind a Nexus boundary (right). Data flows left-to-right — and if the Compliance side goes down mid-check, the payment resumes when it comes back._

<details><summary>What You'll Build</summary>
<details>
<summary>What You'll Build</summary>

You'll start with a monolith where everything — the payment workflow, payment activities, and compliance checks — runs on a single Worker. By the end, you'll have two independent Workers: one for Payments and one for Compliance, communicating through a Nexus boundary.

Expand Down Expand Up @@ -344,7 +345,8 @@ You'll work through these files in order: define the service interface (1), impl

**Files:** [`compliance/temporal/workflow/ComplianceWorkflow.java` ](https://github.com/temporalio/edu-nexus-code/blob/main/java/decouple-monolith/exercise/src/main/java/compliance/temporal/workflow/ComplianceWorkflow.java) and [`ComplianceWorkflowImpl.java`](https://github.com/temporalio/edu-nexus-code/blob/main/java/decouple-monolith/exercise/src/main/java/compliance/temporal/workflow/ComplianceWorkflowImpl.java)

<details><summary><code>ComplianceWorkflowImpl</code> (condensed)</summary>
<details>
<summary><code>ComplianceWorkflowImpl</code> (condensed)</summary>

```java
public class ComplianceWorkflowImpl implements ComplianceWorkflow {
Expand Down Expand Up @@ -450,7 +452,8 @@ Just like the interface needs `@Operation` on every method, the handler class ne
1. `@ServiceImpl(service = ComplianceNexusService.class)` on the class — this links the handler to its service interface so Temporal can route incoming Nexus operations to the correct implementation.
2. `@OperationImpl` on each handler method — this marks the method as the handler for a specific Nexus operation. Without it, Temporal won't know which method handles which operation.

<details><summary>Complete implementation of <code>ComplianceNexusServiceImpl.java</code></summary>
<details>
<summary>Complete implementation of <code>ComplianceNexusServiceImpl.java</code></summary>

```java
@ServiceImpl(service = ComplianceNexusService.class)
Expand Down Expand Up @@ -499,7 +502,8 @@ However, an `@UpdateMethod` requires the Worker to be running at the time of the

![Nexus handle retry diagram: first call starts a workflow and returns a handle, retries reuse the same workflow instead of creating duplicates](./ui/nexus-handle-retry.svg)

<details><summary>Key differences between the two handlers:</summary>
<details>
<summary>Key differences between the two handlers:</summary>

| | `checkCompliance` | `submitReview` |
| -------------- | ----------------------------------- | --------------------------------------------- |
Expand All @@ -514,11 +518,17 @@ However, an `@UpdateMethod` requires the Worker to be running at the time of the

<details>
<summary>Q1: What does <code>@ServiceImpl(service = ComplianceNexusService.class)</code> tell Temporal?</summary>

<code>@ServiceImpl</code> links the handler class to its Nexus service interface. Temporal uses this to route incoming Nexus operations to the correct handler.

</details>

<details><summary>Q2: Why does the handler start a workflow instead of calling <code>ComplianceChecker.checkCompliance()</code> directly?</summary>
Handlers should only use Temporal primitives (workflow starts, queries, updates). Business logic belongs in activities, which are invoked by workflows. This keeps the handler thin and the architecture consistent.</details>
<details>
<summary>Q2: Why does the handler start a workflow instead of calling <code>ComplianceChecker.checkCompliance()</code> directly?</summary>

Handlers should only use Temporal primitives (workflow starts, queries, updates). Business logic belongs in activities, which are invoked by workflows. This keeps the handler thin and the architecture consistent.

</details>

---

Expand Down Expand Up @@ -683,7 +693,8 @@ worker.registerActivitiesImplementations(new ComplianceActivityImpl(checker));

> **Analogy:** You're removing the compliance department from your building and adding a phone extension to their new office. The workflow dials the same number (`checkCompliance`), but the call now routes across the street.

<details><summary>New <code>PaymentsWorkerApp.java</code> Code</summary>
<details>
<summary>New <code>PaymentsWorkerApp.java</code> Code</summary>

```java
package payments.temporal;
Expand Down Expand Up @@ -917,12 +928,19 @@ You should see the review result returned in Terminal 4, and back in Terminal 3,

Test your understanding before moving on:

<details><summary>Q1: Where is the Nexus endpoint name (<code>compliance-endpoint</code>) configured?</summary>
<details>
<summary>Q1: Where is the Nexus endpoint name (<code>compliance-endpoint</code>) configured?</summary>

In <code>PaymentsWorkerApp</code>, via <code>NexusServiceOptions</code> → <code>setEndpoint("compliance-endpoint")</code>. The workflow only knows the service interface. The Worker knows the endpoint. This separation keeps the workflow portable.

</details>

<details><summary>Q2: What happens if the Compliance Worker is down when the Payments workflow calls <code>checkCompliance()</code>?</summary>
The Nexus operation will be retried by Temporal until the <code>scheduleToCloseTimeout</code> expires (10 minutes in our case). If the Compliance Worker comes back within that window, the operation completes successfully. The Payment workflow just waits — no crash, no data loss.</details>
<details>
<summary>Q2: What happens if the Compliance Worker is down when the Payments workflow calls <code>checkCompliance()</code>?</summary>

The Nexus operation will be retried by Temporal until the <code>scheduleToCloseTimeout</code> expires (10 minutes in our case). If the Compliance Worker comes back within that window, the operation completes successfully. The Payment workflow just waits — no crash, no data loss.

</details>

<details>
<summary>Q3: What's the difference between <code>@Service</code><code>/</code><code>@Operation</code> and <code>@ServiceImpl</code><code>/</code><code>@OperationImpl</code>?</summary>
Expand All @@ -936,7 +954,8 @@ Think of it as: the interface is the menu (shared), the handler is the kitchen (

</details>

<details><summary>Q4: What's wrong with using <code>OperationHandler.sync()</code> to back a Nexus operation with a long-running workflow?</summary>
<details>
<summary>Q4: What's wrong with using <code>OperationHandler.sync()</code> to back a Nexus operation with a long-running workflow?</summary>

<code>sync()</code> starts a workflow and blocks for its result in a single handler call. If the Nexus operation retries (which happens during timeouts or transient failures), the handler runs again from scratch — starting a duplicate workflow each time.

Expand Down Expand Up @@ -965,7 +984,8 @@ WorkflowRunOperation.fromWorkflowHandle((ctx, details, input) -> {

</details>

<details><summary>Q5: Why does the handler start a workflow instead of calling <code>ComplianceChecker.checkCompliance()</code> directly?</summary>
<details>
<summary>Q5: Why does the handler start a workflow instead of calling <code>ComplianceChecker.checkCompliance()</code> directly?</summary>

Sync handlers should only contain **Temporal primitives** — workflow starts and queries. Running arbitrary Java code (like <code>ComplianceChecker.checkCompliance()</code>) in a handler bypasses Temporal's durability guarantees.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Below is a demonstration of the application's functionality:

Now it's time to start setting things up.

## Create a new Slack App configuration
## Create a new Slack App configuration {#credentials}

Before you write any code, you need to configure your application with Slack to get your API tokens.
You'll use these to connect your app to the Slack API.
Expand Down
Loading