<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Worker Định Kỳ Check Info BM Gắn Sao — The Operator's Journal</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Noto+Serif:ital,wght@0,400;0,600;0,700;1,400;1,600&family=Noto+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--ink: #0a0a0a;
--paper: #fafafa;
--muted: #6b7280;
--rule: #e5e7eb;
--accent: #c2410c;
--accent-soft: #fff7ed;
--code-bg: #0f172a;
}
html { scroll-behavior: smooth; }
body {
font-family: 'Noto Serif', 'Inter', Georgia, serif;
background: var(--paper);
color: var(--ink);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
.font-display { font-family: 'Noto Serif', Georgia, serif; }
.font-sans-ui { font-family: 'Inter', 'Noto Sans', system-ui, sans-serif; }
.font-mono { font-family: 'JetBrains Mono', ui-monospace, monospace; }
.prose-article {
font-size: 1.125rem;
line-height: 1.75;
color: #1f2937;
max-width: 65ch;
margin-inline: auto;
}
.prose-article p { margin-bottom: 1.5rem; }
.prose-article p.lede {
font-size: 1.375rem;
line-height: 1.6;
color: #111827;
font-weight: 400;
}
.prose-article p.lede::first-letter {
font-family: 'Noto Serif', serif;
font-size: 4.5rem;
line-height: 0.85;
float: left;
margin: 0.5rem 0.75rem 0 0;
color: var(--accent);
font-weight: 700;
}
.prose-article h2 {
font-family: 'Noto Serif', serif;
font-size: 2rem;
font-weight: 700;
margin-top: 4rem;
margin-bottom: 1.25rem;
letter-spacing: -0.01em;
color: #0a0a0a;
}
.prose-article h3 {
font-family: 'Inter', sans-serif;
font-size: 0.8125rem;
text-transform: uppercase;
letter-spacing: 0.18em;
color: var(--accent);
font-weight: 600;
margin-top: 2.5rem;
margin-bottom: 0.75rem;
}
.prose-article a { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.prose-article strong { color: #0a0a0a; font-weight: 600; }
.prose-article em { font-style: italic; }
.prose-article ul, .prose-article ol { margin-bottom: 1.5rem; padding-left: 1.5rem; }
.prose-article ul li { list-style: disc; margin-bottom: 0.5rem; }
.prose-article ol li { list-style: decimal; margin-bottom: 0.5rem; }
.pull-quote {
font-family: 'Noto Serif', serif;
font-style: italic;
font-size: 1.875rem;
line-height: 1.4;
color: #0a0a0a;
border-left: 4px solid var(--accent);
padding: 0.5rem 0 0.5rem 1.5rem;
margin: 3rem 0;
font-weight: 500;
}
.pull-quote cite {
display: block;
font-style: normal;
font-family: 'Inter', sans-serif;
font-size: 0.8125rem;
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--muted);
margin-top: 1rem;
font-weight: 500;
}
figure { margin: 3rem 0; }
figcaption {
font-family: 'Inter', sans-serif;
font-style: italic;
font-size: 0.875rem;
color: var(--muted);
margin-top: 0.75rem;
text-align: center;
line-height: 1.5;
}
.code-block {
background: var(--code-bg);
border-radius: 12px;
overflow: hidden;
margin: 2rem 0;
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.625rem 1.25rem;
background: #1e293b;
color: #94a3b8;
font-family: 'Inter', sans-serif;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.1em;
border-bottom: 1px solid #334155;
}
.code-header .lang-tag {
background: var(--accent);
color: white;
padding: 0.125rem 0.5rem;
border-radius: 4px;
font-weight: 600;
font-size: 0.6875rem;
letter-spacing: 0.08em;
}
.code-body {
padding: 1.25rem;
font-family: 'JetBrains Mono', monospace;
font-size: 0.9rem;
line-height: 1.7;
color: #e2e8f0;
overflow-x: auto;
}
.code-body .tok-key { color: #fbbf24; }
.code-body .tok-str { color: #86efac; }
.code-body .tok-num { color: #f472b6; }
.code-body .tok-com { color: #64748b; font-style: italic; }
.code-body .tok-op { color: #f97316; }
.inline-code {
font-family: 'JetBrains Mono', monospace;
background: #fef3c7;
color: #78350f;
padding: 0.125rem 0.4rem;
border-radius: 4px;
font-size: 0.9em;
}
.rule-thin { border-color: var(--rule); }
.deco-rule {
height: 1px;
background: linear-gradient(to right, transparent, var(--rule) 20%, var(--rule) 80%, transparent);
margin: 2.5rem auto;
max-width: 65ch;
}
.small-caps {
font-family: 'Inter', sans-serif;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.2em;
font-weight: 600;
}
.fade-in {
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.anatomy-token {
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
position: relative;
}
.anatomy-token .label-top {
font-family: 'Inter', sans-serif;
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--accent);
font-weight: 600;
}
.anatomy-token .glyph {
font-family: 'JetBrains Mono', monospace;
font-size: 1.5rem;
font-weight: 600;
color: #0a0a0a;
background: #fff;
padding: 0.5rem 0.75rem;
border: 1px solid var(--rule);
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}
.anatomy-token .label-bot {
font-family: 'Inter', sans-serif;
font-size: 0.75rem;
color: var(--muted);
text-align: center;
line-height: 1.3;
max-width: 80px;
}
.anatomy-sep {
font-family: 'JetBrains Mono', monospace;
font-size: 1.5rem;
color: #cbd5e1;
align-self: center;
padding-top: 0.5rem;
}
.status-pill {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.375rem 0.75rem;
border-radius: 999px;
font-family: 'Inter', sans-serif;
font-size: 0.8125rem;
font-weight: 500;
}
.flow-step {
background: white;
border: 1px solid var(--rule);
border-radius: 12px;
padding: 1rem 1.25rem;
position: relative;
}
.flow-step .step-num {
position: absolute;
top: -0.75rem;
left: 1.25rem;
background: var(--accent);
color: white;
width: 1.5rem;
height: 1.5rem;
border-radius: 999px;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Inter', sans-serif;
font-size: 0.75rem;
font-weight: 700;
}
.field-table {
width: 100%;
border-collapse: collapse;
font-family: 'Inter', sans-serif;
}
.field-table th {
text-align: left;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--muted);
font-weight: 600;
padding: 0.75rem 1rem;
border-bottom: 1.5px solid #0a0a0a;
}
.field-table td {
padding: 0.875rem 1rem;
border-bottom: 1px solid var(--rule);
font-size: 0.9375rem;
vertical-align: top;
}
.field-table tr:hover { background: #fafafa; }
.field-table .col-field { font-family: 'JetBrains Mono', monospace; font-weight: 600; color: var(--accent); }
.field-table .col-rule { color: var(--muted); }
.related-card {
background: white;
border: 1px solid var(--rule);
border-radius: 16px;
padding: 1.5rem;
transition: all 0.2s ease;
cursor: pointer;
}
.related-card:hover {
border-color: #0a0a0a;
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0,0,0,0.06);
}
.error-stripe {
background: repeating-linear-gradient(
45deg,
#fef2f2,
#fef2f2 10px,
#fee2e2 10px,
#fee2e2 20px
);
}
.margin-note {
font-family: 'Inter', sans-serif;
font-size: 0.8125rem;
line-height: 1.5;
color: var(--muted);
padding-left: 1rem;
border-left: 2px solid var(--rule);
}
.focus\:ring-accent:focus {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
</style>
</head>
<body class="bg-[#fafafa]">
<header class="border-b-2 border-[#0a0a0a]">
<div class="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
<div class="flex items-center gap-4">
<svg width="36" height="36" viewBox="0 0 36 36" fill="none">
<rect x="2" y="2" width="32" height="32" rx="6" fill="#0a0a0a"/>
<path d="M11 18 L17 24 L25 12" stroke="#fafafa" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<div>
<div class="font-display font-bold text-xl tracking-tight leading-none">The Operator's Journal</div>
<div class="font-sans-ui text-xs text-gray-500 mt-0.5 tracking-wide">Product specs, ops playbooks & internal tooling notes</div>
</div>
</div>
<nav class="hidden md:flex items-center gap-6 font-sans-ui text-sm text-gray-700">
<a href="#" class="hover:text-[#c2410c]">Specs</a>
<a href="#" class="hover:text-[#c2410c]">Playbooks</a>
<a href="#" class="hover:text-[#c2410c]">Postmortems</a>
<a href="#" class="hover:text-[#c2410c]">Archive</a>
</nav>
<div class="font-sans-ui text-xs text-gray-500 tracking-wider uppercase">Vol. 04 · Issue 19</div>
</div>
</header>
<section class="border-b border-gray-200 fade-in">
<div class="max-w-4xl mx-auto px-6 py-20 md:py-28">
<div class="flex items-center gap-3 mb-8">
<span class="small-caps text-[#c2410c]">Product Spec</span>
<span class="text-gray-300">·</span>
<span class="small-caps text-gray-500">Background worker · BM monitoring</span>
<span class="text-gray-300">·</span>
<span class="small-caps text-gray-500">NolimitHub · BM gắn sao</span>
</div>
<h1 class="font-display font-bold text-5xl md:text-7xl leading-[1.05] tracking-tight text-[#0a0a0a]">
Worker Định Kỳ Check Info BM Gắn Sao: 6 Tiếng / Lần, Tự Động, Không Phụ Thuộc Extension
</h1>
<p class="font-display text-xl md:text-2xl text-gray-600 mt-8 italic leading-relaxed max-w-3xl">
Đặc tả bổ sung một job định kỳ vào worker chạy ngầm đã có sẵn — lấy danh sách IDBM gắn sao mỗi 6 tiếng, check info từ Facebook, đối chiếu với lần trước, cập nhật DB + lưu lịch sử + cảnh báo NolimitHub khi có biến động. Không dựng worker mới, không phụ thuộc Extension chạy tay.
</p>
<div class="mt-12 flex flex-wrap items-center gap-x-8 gap-y-4 pt-6 border-t border-gray-200">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-full bg-[#0a0a0a] text-[#fafafa] flex items-center justify-center font-display font-bold text-lg">D</div>
<div>
<div class="font-sans-ui font-semibold text-sm text-[#0a0a0a]">Doanh Nguyễn</div>
<div class="font-sans-ui text-xs text-gray-500">Editor, The Operator's Journal</div>
</div>
</div>
<div class="font-sans-ui text-sm text-gray-500">
<span class="small-caps text-gray-400">Published</span>
<span class="ml-2">05 Jun 2026</span>
</div>
<div class="font-sans-ui text-sm text-gray-500">
<span class="small-caps text-gray-400">Read time</span>
<span class="ml-2">~ 6 min</span>
</div>
<div class="font-sans-ui text-sm text-gray-500">
<span class="small-caps text-gray-400">Priority</span>
<span class="ml-2">P1 — High</span>
</div>
</div>
</div>
</section>
<section class="border-b border-gray-200 bg-white">
<div class="max-w-4xl mx-auto px-6 py-10">
<div class="small-caps text-gray-400 mb-4">In this spec</div>
<ol class="grid md:grid-cols-2 gap-x-12 gap-y-2 font-sans-ui text-sm">
<li class="flex gap-3"><span class="text-gray-400 font-mono">01</span><a href="#info" class="hover:text-[#c2410c] text-gray-800">Thông tin chung</a></li>
<li class="flex gap-3"><span class="text-gray-400 font-mono">02</span><a href="#context" class="hover:text-[#c2410c] text-gray-800">Bối cảnh</a></li>
<li class="flex gap-3"><span class="text-gray-400 font-mono">03</span><a href="#objectives" class="hover:text-[#c2410c] text-gray-800">Mục tiêu & Chỉ số</a></li>
</ol>
</div>
</section>
<article class="py-20">
<div class="prose-article">
<p class="lede">
BM gắn sao là nhóm BM <em>trọng yếu nhất</em> của hệ thống — nhưng hiện đang được làm mới info chỉ khi ai đó <em>bật Extension</em>. Tần suất đó đã giảm; một BM gắn sao có thể die/bị hạn chế nhiều ngày mà không ai phát hiện kịp. Bài này đặc tả một thay đổi mỏng: <strong>thêm 1 job định kỳ (6h/lần) vào worker chạy ngầm đã có</strong> — tự lấy IDBM gắn sao, check info, so với lần trước, cập nhật DB + lưu lịch sử + cảnh báo NolimitHub khi có biến động.
</p>
<h2 id="info">01 · Thông tin chung</h2>
<h3>Bốn trường meta cần đọc đầu tiên</h3>
<p>
Trước khi đọc bất kỳ phần nào khác, hãy đối chiếu bốn ô dưới đây để chắc rằng bạn đang đọc đúng phiên bản tài liệu và đúng feature. <strong>Feature ID</strong> là khoá nối tới ticket, <strong>Status</strong> cho biết tài liệu đã sẵn sàng để code hay chưa.
</p>
</div>
<figure class="max-w-3xl mx-auto px-6">
<div class="border border-gray-200 rounded-2xl bg-white p-8 shadow-sm">
<div class="grid md:grid-cols-2 gap-6">
<div class="flex gap-4">
<div class="flex-shrink-0 w-9 h-9 rounded-lg bg-orange-50 text-[#c2410c] flex items-center justify-center font-mono font-bold">ID</div>
<div>
<div class="font-sans-ui font-semibold text-[#0a0a0a] mb-1">Feature ID</div>
<div class="font-sans-ui text-sm text-gray-600 leading-relaxed"><span class="inline-code">FT-WORKER-CHECK-BM-STARRED-01</span> — khoá nối tới ticket & branch.</div>
</div>
</div>
<div class="flex gap-4">
<div class="flex-shrink-0 w-9 h-9 rounded-lg bg-orange-50 text-[#c2410c] flex items-center justify-center font-mono font-bold">PO</div>
<div>
<div class="font-sans-ui font-semibold text-[#0a0a0a] mb-1">Owner / PO</div>
<div class="font-sans-ui text-sm text-gray-600 leading-relaxed"><strong>CTO</strong> — cũng là người Approve cuối cùng trước khi deploy Production.</div>
</div>
</div>
<div class="flex gap-4">
<div class="flex-shrink-0 w-9 h-9 rounded-lg bg-orange-50 text-[#c2410c] flex items-center justify-center font-mono font-bold">P1</div>
<div>
<div class="font-sans-ui font-semibold text-[#0a0a0a] mb-1">Priority</div>
<div class="font-sans-ui text-sm text-gray-600 leading-relaxed"><strong>P1 — High</strong>: BM gắn sao là BM trọng yếu; die/restrict âm thầm hoặc info sai lệch gây gián đoạn khách & rủi ro tài chính.</div>
</div>
</div>
<div class="flex gap-4">
<div class="flex-shrink-0 w-9 h-9 rounded-lg bg-orange-50 text-[#c2410c] flex items-center justify-center font-mono font-bold">DR</div>
<div>
<div class="font-sans-ui font-semibold text-[#0a0a0a] mb-1">Status</div>
<div class="font-sans-ui text-sm text-gray-600 leading-relaxed"><strong>Draft</strong> — tạo 05/06/2026, cập nhật cuối 05/06/2026.</div>
</div>
</div>
</div>
</div>
<figcaption>Hình 1.1 — Bốn trường meta của feature. ID / PO / Priority / Status — đối chiếu trước khi đọc tiếp.</figcaption>
</figure>
<div class="prose-article">
<h2 id="context">02 · Bối cảnh</h2>
<h3>Hai vấn đề hiện tại — và đâu là nguyên nhân gốc</h3>
<p>
<strong>BM gắn sao</strong> là các Business Manager được đánh dấu trọng yếu trong hệ thống (BM giá trị cao / đang phục vụ khách quan trọng). Đúng ra đây là nhóm cần được giám sát chặt nhất, nhưng hiện việc nắm info của chúng đang gặp vấn đề:
</p>
<ul>
<li><strong>Phụ thuộc vào Extension chạy tay:</strong> hiện info BM chỉ được làm mới khi Admin/vận hành <em>bật Extension</em> — lúc đó Extension mới đồng bộ và gửi info BM về, Admin/vận hành mới nắm được info hiện tại của BM. Tức là muốn biết info mới thì phải có người chủ động bật Extension.</li>
<li><strong>Tần suất đồng bộ qua Extension đã giảm:</strong> info BM gắn sao <em>không còn được cập nhật đều</em> → dữ liệu trong hệ thống là dữ liệu cũ, sai lệch so với thực tế trên Facebook. Đặc biệt <strong>trạng thái Live/Die/Restricted</strong> và info định danh của BM thay đổi liên tục, nên một BM gắn sao có thể đã die/bị hạn chế nhiều ngày mà <em>không ai phát hiện kịp</em>.</li>
</ul>
<p>
<strong>Nguyên nhân gốc:</strong> việc làm mới info BM gắn sao đang <em>phụ thuộc thao tác tay (bật Extension)</em> và tần suất đó đã giảm; hệ thống chưa có cơ chế <strong>tự động, định kỳ</strong> đồng bộ lại info cho riêng nhóm BM gắn sao. Vì vậy dữ liệu vừa trễ, vừa thiếu, vừa không có lịch sử để truy vết khi BM "đổi trạng thái".
</p>
<p>
Tính năng cần bổ sung: thêm vào <strong>worker chạy ngầm đã có sẵn</strong> một chức năng định kỳ (mỗi <strong>6 tiếng/lần</strong>):
</p>
<ol>
<li>Lấy toàn bộ <strong>IDBM của các BM đang gắn sao</strong> trong hệ thống.</li>
<li>Tự gọi check info từng BM (quan trọng nhất là <em>info BM</em> và <em>trạng thái Live/Die/Restricted</em>), không còn phải chờ ai bật Extension.</li>
<li>Đối chiếu info mới với lần check trước; khi có thay đổi thì <strong>cập nhật DB + lưu lịch sử thay đổi + gửi cảnh báo về NolimitHub</strong>.</li>
</ol>
<div class="code-block">
<div class="code-header">
<span>bm-check.shape</span>
<span class="lang-tag">SCHEMA</span>
</div>
<div class="code-body">
<span class="tok-com">// Một lần check info BM gắn sao trong chu kỳ 6h</span>
<span class="tok-key">BMCheck</span> <span class="tok-op">=</span> {
<span class="tok-key">bm_id</span><span class="tok-op">:</span> <span class="tok-str">"ID của Business Manager"</span><span class="tok-op">,</span>
<span class="tok-key">starred</span><span class="tok-op">:</span> <span class="tok-key">true</span><span class="tok-op">,</span> <span class="tok-com">// scope: chỉ BM gắn sao</span>
<span class="tok-key">status</span><span class="tok-op">:</span> <span class="tok-str">"Live | Die | Restricted"</span><span class="tok-op">,</span>
<span class="tok-key">info</span><span class="tok-op">:</span> <span class="tok-str">"các trường info định danh của BM"</span><span class="tok-op">,</span>
<span class="tok-key">last_checked_at</span><span class="tok-op">:</span> <span class="tok-str">"mốc thời gian check gần nhất"</span><span class="tok-op">,</span>
<span class="tok-key">cycle</span><span class="tok-op">:</span> <span class="tok-num">6</span> <span class="tok-com">// tiếng/lần — nên cấu hình được</span>
}
</div>
</div>
<div class="pull-quote">
Nguyên nhân gốc: việc làm mới info BM gắn sao đang phụ thuộc thao tác tay (bật Extension), và tần suất đó đã giảm — hệ thống chưa có cơ chế tự động, định kỳ đồng bộ cho riêng nhóm trọng yếu.
<cite>— Vì sao đây là vấn đề thiết kế, không phải vấn đề kênh</cite>
</div>
<h2 id="objectives">03 · Mục tiêu & Chỉ số</h2>
<h3>Ba chỉ số đo lường thành công</h3>
<p>
<strong>Sau khi làm xong:</strong> info của <em>toàn bộ BM gắn sao</em> luôn được làm mới <strong>tự động</strong>, độ trễ tối đa <strong>6 tiếng</strong> so với thực tế trên Facebook — không còn phụ thuộc việc Admin/vận hành bật Extension thủ công. Hệ thống <strong>phát hiện sớm</strong> BM gắn sao bị die/restrict hoặc đổi info trọng yếu, và <strong>cảnh báo ngay về NolimitHub</strong> để xử lý kịp thời.
</p>
<p>
Ngoài ra, hệ thống còn có <strong>lịch sử biến động info BM</strong> theo thời gian để truy vết (BM đổi trạng thái lúc nào, từ gì sang gì) — phục vụ cho điều tra sự cố và đối chiếu khi khách báo lỗi.
</p>
</div>
<figure class="max-w-3xl mx-auto px-6">
<div class="grid md:grid-cols-3 gap-4">
<div class="bg-[#fff7ed] border border-orange-200 rounded-xl p-5">
<div class="small-caps text-[#c2410c] mb-2">Metric · 01</div>
<div class="font-sans-ui font-semibold text-[#0a0a0a]">Phủ 100%</div>
<div class="font-sans-ui text-sm text-gray-600 mt-2 leading-relaxed">100% BM gắn sao được check tự động trong mỗi chu kỳ 6h — không bỏ sót, không phụ thuộc Extension.</div>
</div>
<div class="bg-[#fff7ed] border border-orange-200 rounded-xl p-5">
<div class="small-caps text-[#c2410c] mb-2">Metric · 02</div>
<div class="font-sans-ui font-semibold text-[#0a0a0a]">Phát hiện ≤ 6h</div>
<div class="font-sans-ui text-sm text-gray-600 mt-2 leading-relaxed">Thời gian phát hiện một BM gắn sao die/restrict giảm từ <em>"tới khi có người bật Extension / khách báo"</em> xuống <strong>≤ 6 tiếng</strong>.</div>
</div>
<div class="bg-[#fff7ed] border border-orange-200 rounded-xl p-5">
<div class="small-caps text-[#c2410c] mb-2">Metric · 03</div>
<div class="font-sans-ui font-semibold text-[#0a0a0a]">Hết bật Extension tay</div>
<div class="font-sans-ui text-sm text-gray-600 mt-2 leading-relaxed">Giảm gần như về 0 việc phải bật Extension thủ công chỉ để nắm info BM gắn sao.</div>
</div>
</div>
<figcaption>Hình 3.1 — Ba chỉ số đo lường thành công. Metric 02 là chỉ số then chốt — giảm thời gian phát hiện die/restrict từ "tới khi có người bật Extension / khách báo" về "≤ 6 tiếng".</figcaption>
</figure>
<div class="prose-article">
<hr class="deco-rule">
<p class="text-gray-500 italic !mb-0">
— Last reviewed: 05/06/2026. Phản hồi gửi về <a href="mailto:owaf.fakku@gmail.com">owaf.fakku@gmail.com</a>.
</p>
</div>
</article>
<section class="bg-white border-t border-b border-gray-200 py-16">
<div class="max-w-4xl mx-auto px-6">
<div class="flex flex-col md:flex-row gap-8 items-start">
<div class="flex-shrink-0">
<div class="w-24 h-24 rounded-2xl bg-gradient-to-br from-[#0a0a0a] to-[#374151] text-[#fafafa] flex items-center justify-center font-display font-bold text-4xl shadow-lg">D</div>
</div>
<div class="flex-1">
<div class="small-caps text-gray-400 mb-2">About the author</div>
<h3 class="font-display font-bold text-2xl text-[#0a0a0a]">Doanh Nguyễn</h3>
<p class="font-sans-ui text-gray-600 mt-3 leading-relaxed max-w-2xl">
Editor của <em>The Operator's Journal</em>, viết về product spec cho công cụ nội bộ, hệ thống giám sát tự động, và những lớp "tự động hoá thay cho thao tác tay" giúp đội vận hành không bị bào mòn bởi việc bật/tắt Extension. Trước đây xây dựng dashboard thuê TKQC cho các tổ chức ở Đông Nam Á — nơi câu hỏi "BM này còn sống không, hay vừa bị FB đánh die?" lặp lại mỗi ngày.
</p>
<div class="mt-4 flex gap-4 font-sans-ui text-sm">
<a href="#" class="text-[#c2410c] hover:underline">More articles →</a>
<a href="#" class="text-gray-600 hover:text-[#0a0a0a]">Subscribe</a>
<a href="#" class="text-gray-600 hover:text-[#0a0a0a]">@doanh</a>
</div>
</div>
</div>
</div>
</section>
<section class="py-20">
<div class="max-w-6xl mx-auto px-6">
<div class="flex items-end justify-between mb-10 border-b border-gray-200 pb-6">
<h3 class="font-display font-bold text-3xl text-[#0a0a0a]">Tiếp tục đọc</h3>
<a href="#" class="font-sans-ui text-sm text-[#c2410c] hover:underline">Xem toàn bộ archive →</a>
</div>
<div class="grid md:grid-cols-3 gap-6">
<article class="related-card">
<div class="small-caps text-[#c2410c] mb-3">Spec</div>
<h4 class="font-display font-bold text-xl text-[#0a0a0a] leading-snug">Cảnh báo bill lệch giữa Activity & Gate: bổ sung ID, đánh dấu, chống lặp</h4>
<p class="font-sans-ui text-sm text-gray-600 mt-3 leading-relaxed">Spec anh em — cũng đụng tới NolimitHub và chu kỳ quét của worker. Đáng đọc cùng để hiểu cách thiết kế cảnh báo "actionable + không spam".</p>
<div class="mt-5 flex items-center gap-3 font-sans-ui text-xs text-gray-500">
<span>Doanh Nguyễn</span>
<span class="text-gray-300">·</span>
<span>16 min read</span>
</div>
</article>
<article class="related-card">
<div class="small-caps text-[#c2410c] mb-3">Pattern</div>
<h4 class="font-display font-bold text-xl text-[#0a0a0a] leading-snug">Khi tự động hoá thay thế Extension chạy tay: ranh giới và cạm bẫy</h4>
<p class="font-sans-ui text-sm text-gray-600 mt-3 leading-relaxed">Khi nào nên thay thao tác tay bằng worker định kỳ, khi nào nên giữ — và làm sao để worker mới không gián đoạn các job đang chạy trên cùng một process.</p>
<div class="mt-5 flex items-center gap-3 font-sans-ui text-xs text-gray-500">
<span>Doanh Nguyễn</span>
<span class="text-gray-300">·</span>
<span>11 min read</span>
</div>
</article>
<article class="related-card">
<div class="small-caps text-[#c2410c] mb-3">Pattern</div>
<h4 class="font-display font-bold text-xl text-[#0a0a0a] leading-snug">Phân biệt "die thật" và "lỗi gọi tạm thời" trong các job check Facebook</h4>
<p class="font-sans-ui text-sm text-gray-600 mt-3 leading-relaxed">Rate-limit và timeout của FB thường bị hiểu nhầm là tài khoản die. Bài này phân tích cơ chế retry / độ trễ để tránh báo nhầm trong các worker check định kỳ.</p>
<div class="mt-5 flex items-center gap-3 font-sans-ui text-xs text-gray-500">
<span>Doanh Nguyễn</span>
<span class="text-gray-300">·</span>
<span>9 min read</span>
</div>
</article>
</div>
</div>
</section>
<footer class="bg-[#0a0a0a] text-[#fafafa] py-12 mt-10">
<div class="max-w-6xl mx-auto px-6 flex flex-col md:flex-row justify-between gap-6">
<div>
<div class="font-display font-bold text-xl">The Operator's Journal</div>
<div class="font-sans-ui text-sm text-gray-400 mt-2">Product specs, ops playbooks & internal tooling notes.</div>
</div>
<div class="flex gap-8 font-sans-ui text-sm text-gray-400">
<div>
<div class="small-caps text-gray-500 mb-2">Sections</div>
<div class="space-y-1">
<div>Specs</div>
<div>Playbooks</div>
<div>Postmortems</div>
</div>
</div>
<div>
<div class="small-caps text-gray-500 mb-2">About</div>
<div class="space-y-1">
<div>Editor</div>
<div>Subscribe</div>
<div>RSS</div>
</div>
</div>
</div>
</div>
<div class="max-w-6xl mx-auto px-6 mt-10 pt-6 border-t border-gray-800 font-sans-ui text-xs text-gray-500 flex justify-between">
<span>© 2026 The Operator's Journal · Vol. 04 Issue 19</span>
<span>Published 05 Jun 2026 · Hà Nội / Sài Gòn</span>
</div>
</footer>
</body>
</html>
Số dòng: 631