Skip to content
Open
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ All created via code. Use or repurpose the content to fit your next Dev Day!
| Child Workflows | Foundations | Java · child workflows | [`segments/14-child-workflows.md`](decks/java-master/segments/14-child-workflows.md) | [`workshops/child-workflows/`](workshops/child-workflows/) | [`child-workflows-java`](https://play.instruqt.com/temporal/tracks/child-workflows-java) | 15 min | Nikolay Advolodkin | Opus 5.5 (100%) | Pending | 3.98 |
| Continue-As-New | Advanced | Java · event history | [`segments/15-continue-as-new.md`](decks/java-master/segments/15-continue-as-new.md) | [`workshops/continue-as-new-subscription/`](workshops/continue-as-new-subscription/) | [`continue-as-new-java`](https://play.instruqt.com/temporal/tracks/continue-as-new-java) | 10 min | Nikolay Advolodkin | Opus 5.5 (100%) | Pending | 2.86 |
| Activity Heartbeats | Advanced | Java · activities | [`segments/16-heartbeating.md`](decks/java-master/segments/16-heartbeating.md) | [`workshops/heartbeats-shipment/`](workshops/heartbeats-shipment/) | [`heartbeats-java`](https://play.instruqt.com/temporal/tracks/heartbeats-java) | 10 min | Nikolay Advolodkin | Opus 5.5 (100%) | Pending | 4.45 |
| Temporal Essentials | Foundations | Java · Workflows, Activities, Workers, Clients, Signals, Queries, Updates, Sagas, versioning, timeouts and retries, observability | [`decks/java-master/`](decks/java-master/) | Deck only; code slides proven in [`snippets/`](decks/java-master/snippets/) | per section, linked in the Deck | Pending | Nikolay Advolodkin | Opus 5.5 (100%) | [112.15](docs/authoring/java-master-deck.md) | — |
| Temporal Essentials | Foundations | Java · Workflows, Activities, Workers, Clients, Signals, Queries, Updates, Sagas, versioning, timeouts and retries, observability | [`decks/java-master/`](decks/java-master/) | Deck only; code slides proven in [`snippets/`](decks/java-master/snippets/) | one invite link for every lab, in the Deck | Pending | Nikolay Advolodkin | Opus 5.5 (100%) | [112.15](docs/authoring/java-master-deck.md) | — |

**Temporal Essentials is a Deck, not a Workshop**: it has no Track, Exercise or
Solution of its own, so its Track cost is a dash. It is the Foundations Deck
Expand Down
82 changes: 82 additions & 0 deletions decks/java-master/components/CopyLink.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<!--
ABOUTME: A link with a copy-to-clipboard button beside it, for the lab URL on
ABOUTME: the exercise slides.

<CopyLink href="https://t.mp/ddnov122026" />

The label defaults to the href without its scheme. Clicks stop here, so
pressing the button copies instead of advancing the slide. Copying uses the
Clipboard API, with a hidden-textarea fallback for when it is unavailable
(an insecure origin, or a browser that refuses).
-->
<script setup lang="ts">
import { computed, ref } from 'vue'

const props = defineProps<{ href: string; label?: string }>()
const text = computed(() => props.label ?? props.href.replace(/^https?:\/\//, ''))
const copied = ref(false)
let timer: ReturnType<typeof setTimeout> | undefined

function fallbackCopy(value: string): void {
const area = document.createElement('textarea')
area.value = value
area.setAttribute('readonly', '')
area.style.position = 'fixed'
area.style.opacity = '0'
document.body.appendChild(area)
area.select()
document.execCommand('copy')
area.remove()
}

async function copy(): Promise<void> {
try {
await navigator.clipboard.writeText(props.href)
} catch {
fallbackCopy(props.href)
}
copied.value = true
clearTimeout(timer)
timer = setTimeout(() => (copied.value = false), 1500)
}
</script>

<template>
<span class="copy-link" @click.stop>
<a :href="href" target="_blank" rel="noopener">{{ text }}</a>
<button type="button" :class="{ copied }" :aria-label="copied ? 'Copied' : `Copy ${text}`" @click.stop="copy">
{{ copied ? 'Copied' : 'Copy' }}
</button>
</span>
</template>

<style scoped>
.copy-link {
display: inline-flex;
align-items: center;
gap: 0.6rem;
}
.copy-link a {
font-weight: 700;
}
button {
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 0.25rem 0.6rem;
border-radius: 999px;
border: 1px solid var(--temporal-border-hi);
color: var(--temporal-text-strong);
background: rgb(154 128 255 / 15%);
cursor: pointer;
}
button:hover {
border-color: var(--temporal-lavender);
}
button.copied {
color: var(--temporal-green);
border-color: var(--temporal-green);
background: rgba(89, 253, 160, 0.12);
}
</style>
31 changes: 14 additions & 17 deletions decks/java-master/components/ResumableTransfer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@

<ResumableTransfer />

Slide clicks step through the four engineer actions (the slide needs
`clicks: 4`), so arrow keys and a clicker work; each button jumps to its
action, and the next one is outlined. Each step shows the CLI command an
Slide clicks step through the three engineer actions (the slide needs
`clicks: 3`), so arrow keys and a clicker work; each button jumps to its
action. The action just performed is outlined, so the button always matches
the terminal and the Events beside it; before the first click, a dashed
outline points at action 1. Each step shows the CLI command an
engineer would run, its output, and the Events it adds. Workflow Task events are left out, as the UI's compact
view does. Everything here matches the resumable package in snippets/
(TransferWorkflowImpl.java, TransferActivitiesImpl.java) and what
ResumableTest checks: one attempt on a non-retryable failure, a Query that
adds nothing to history, a malformed fix that is recorded but ignored, both
Signals in history, the Java SDK's default type names (TransferWorkflow,
ExecuteTransfer), and one Workflow run from start to finish.
adds nothing to history, a COMPLETED status at the end, the Java SDK's
default type names (TransferWorkflow, ExecuteTransfer), and one Workflow run
from start to finish.
-->
<script setup lang="ts">
import { computed } from 'vue'
Expand All @@ -38,7 +40,6 @@ const STARTED: Row[] = [
{ event: 'ActivityTaskStarted', detail: 'attempt 1', tone: 'plain' },
{ event: 'ActivityTaskFailed', detail: 'AccountNotFoundError · non-retryable', tone: 'fail' },
]
const BAD_FIX: Row = { event: 'WorkflowExecutionSignaled', detail: 'retryWithCorrection "acct 1234"', tone: 'signal' }
const GOOD_FIX: Row[] = [
{ event: 'WorkflowExecutionSignaled', detail: 'retryWithCorrection "12345678"', tone: 'signal' },
{ event: 'ActivityTaskScheduled', detail: 'ExecuteTransfer → 12345678', tone: 'plain' },
Expand Down Expand Up @@ -66,19 +67,14 @@ const STEPS: Step[] = [
say: 'It tells you exactly where it is stuck. Look at the history: <strong>no new Events</strong>. A Query is a read.',
},
{
stage: 'parked', status: 'AWAITING_CORRECTION', account: '99999999', rows: [...STARTED, BAD_FIX], fresh: 1,
term: { cmd: signal('acct 1234'), out: 'Signal workflow succeeded' },
say: 'Typo under pressure. The Signal is <strong>recorded</strong>, but the handler validates the format and drops it. Still parked, no transfer attempted.',
},
{
stage: 'done', status: 'COMPLETED', account: '12345678', rows: [...STARTED, BAD_FIX, ...GOOD_FIX], fresh: GOOD_FIX.length,
stage: 'done', status: 'COMPLETED', account: '12345678', rows: [...STARTED, ...GOOD_FIX], fresh: GOOD_FIX.length,
term: { cmd: signal('12345678'), out: 'Signal workflow succeeded' },
say: 'The same Workflow wakes up and retries with your fix. <strong>One run, one history</strong>: the failure, your correction and the success, in order. The customer never re-entered anything.',
},
]

// The buttons name the action each click performs, and jump to it when pressed.
const ACTIONS = ['Send the transfer', 'Query getStatus', 'Signal fix: "acct 1234"', 'Signal fix: "12345678"']
const ACTIONS = ['Send the transfer', 'Query getStatus', 'Signal fix: "12345678"']

const { $clicks, $page } = useSlideContext()
const nav = useNav()
Expand Down Expand Up @@ -107,7 +103,7 @@ const badge = computed(() => ({ idle: 'Not started', parked: 'Running', done: 'C
<button
v-for="(label, i) in ACTIONS"
:key="label"
:class="{ next: i === at, done: i < at }"
:class="{ current: i === at - 1, next: at === 0 && i === 0, done: i < at - 1 }"
@click.stop="go(i + 1)"
>
<span class="n">{{ i + 1 }}</span>{{ label }}
Expand Down Expand Up @@ -186,9 +182,10 @@ button {
}
button:hover { background: rgb(154 128 255 / 35%); }
button .n { display: inline-block; width: 1.1em; opacity: 0.6; font-variant-numeric: tabular-nums; }
button.next { box-shadow: 0 0 0 2px var(--temporal-green); }
button.current { box-shadow: 0 0 0 2px var(--temporal-green); }
button.next { outline: 2px dashed var(--temporal-green); outline-offset: 1px; }
button.done { opacity: 0.4; }
button.reset { grid-column: span 2; text-align: center; background: none; border-color: var(--temporal-border); font-size: 0.6rem; }
button.reset { text-align: center; background: none; border-color: var(--temporal-border); font-size: 0.6rem; }

.term { background: #0b1020; flex: 1 1 auto; min-height: 4.2rem; }
.cmd, .out {
Expand Down
13 changes: 7 additions & 6 deletions decks/java-master/components/WorkflowRun.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { useSlideContext } from '@slidev/client'

type Kind = 'worker' | 'command' | 'event' | 'task'
type Pill = { icon: string; text: string; mono?: boolean; dim?: boolean }
type Step = { kind: Kind; lane: number; pills: Pill[]; say: string }
// `dx` nudges a step right of its slot when its pill is too wide for the gap.
type Step = { kind: Kind; lane: number; pills: Pill[]; say: string; dx?: number }

const LANES = [
{ icon: '⚙️', label: 'Worker' },
Expand All @@ -24,12 +25,12 @@ const LANE_Y = [62, 196, 300]
const COMMAND_Y = 129

const STEPS: Step[] = [
{ kind: 'worker', lane: 0, pills: [{ icon: '▶️', text: 'chargeCard()', mono: true }], say: 'Your Workflow code calls the Activity' },
{ kind: 'worker', lane: 0, pills: [{ icon: '▶️', text: 'activities.chargeCard()', mono: true }], say: 'Your Workflow calls the chargeCard Activity. Nothing runs yet: the call is a request' },
{ kind: 'command', lane: -1, pills: [{ icon: '📨', text: 'ScheduleActivityTask', mono: true }], say: 'The call becomes a Command, sent when the Workflow Task ends' },
{ kind: 'event', lane: 1, pills: [{ icon: '📝', text: 'ActivityTaskScheduled', mono: true }], say: 'The Service writes it down' },
{ kind: 'task', lane: 2, pills: [{ icon: '🎫', text: 'Activity Task' }], say: 'An Activity Task waits on the queue' },
{ kind: 'task', lane: 2, pills: [{ icon: '🎫', text: 'Activity Task' }], say: 'An Activity Task waits on the queue: the Activity Type, ChargeCard, and an Activity ID' },
{ kind: 'worker', lane: 0, pills: [{ icon: '🙋', text: 'poll' }], say: 'A Worker polls and takes it' },
{ kind: 'worker', lane: 0, pills: [{ icon: '💳', text: 'charge' }], say: 'The Worker runs chargeCard' },
{ kind: 'worker', lane: 0, dx: 30, pills: [{ icon: '💳', text: 'chargeCard runs', mono: true }], say: 'The Worker matches ChargeCard to the method it registered at startup, and runs it' },
{
kind: 'event',
lane: 1,
Expand All @@ -40,7 +41,7 @@ const STEPS: Step[] = [
say: 'The result is recorded. Started is written now too',
},
{ kind: 'task', lane: 2, pills: [{ icon: '🎫', text: 'Workflow Task' }], say: 'A new Workflow Task wakes the Workflow' },
{ kind: 'worker', lane: 0, pills: [{ icon: '🔁', text: 'replay → next line' }], say: 'Replay returns the result. The next line runs' },
{ kind: 'worker', lane: 0, pills: [{ icon: '↩️', text: 'chargeCard() returns' }], say: 'A Worker takes the Workflow Task: activities.chargeCard() returns the result, and the next line runs' },
]

const LEGEND: { kind: Kind; label: string }[] = [
Expand All @@ -59,7 +60,7 @@ function width(p: Pill): number {
}
function centre(i: number): { x: number; y: number } {
const s = STEPS[i]
return { x: X0 + i * DX, y: s.lane < 0 ? COMMAND_Y : LANE_Y[s.lane] }
return { x: X0 + i * DX + (s.dx ?? 0), y: s.lane < 0 ? COMMAND_Y : LANE_Y[s.lane] }
}
function pillY(i: number, j: number): number {
const n = STEPS[i].pills.length
Expand Down
Loading
Loading