# İçerik Atölyesi

Tasarım ekranı, remote config key'leri ve final metinler tek ekranda.
Üç rolün aynı tabloda çalıştığı, çıktısı yazılım ekibinin doğrudan kullanabileceği
bir CSV olan içerik akışı.

Canlı: `4pflows.pages.dev/copy/` · Pilot proje: `?p=hard-paywall`

## Kim ne yapar

| Rol | Girdi | Nerede |
|---|---|---|
| **Tasarım** | Ekran maketi, dummy metin, karakter limiti, metnin nerede durduğu | `content.json` + `screens/*.html` |
| **Yazılım** | Remote config key'i, tip | `content.json` içindeki `key` alanı |
| **Metin** | Final metin, statü, not | Atölye arayüzü (tarayıcıda, kayıt otomatik) |
| **Yazılım (çıkış)** | `keys.csv` → remote config | Atölyedeki **keys.csv** düğmesi |

Metin yazarı hiçbir dosyaya dokunmaz: linke girer, maketi görür, sağdaki
alanlara yazar. Yazdığı anda kaydedilir.

## İki görünüm

Üst çubuktaki **Tablo / Akış** anahtarı neye baktığını belirler:

- **Tablo** — solda tek bir ekranın maketi, sağda o ekrana ait key'ler ve yazım alanları.
  Metin yazarken kullanılan görünüm.
- **Akış** — bütün ekranlar bağlantılarıyla birlikte tek sayfada. Ana yol soldan sağa
  tek kulvarda okunur, sapmalar (yan yollar, hata durumları) alttaki kulvarlara iner.
  Ekran başlığındaki sayı o ekranda kaç key'in yazıldığını, ▲ tasarıma sığmayan metni
  gösterir. Bir ekrana tıklamak Tablo görünümünde o ekranın satırlarına götürür.
  Geri dönüşler kesikli çizilir ve en alttaki şeritten dolanır. **Sıkı / Rahat**
  anahtarı akışın sıkılığını değiştirir — sıkı, `content.json` içindeki
  `flow.scale` ölçeğidir; rahat onun 15 puan üstü. Yanındaki kaydırıcı ölçeği
  serbestçe ayarlar.

Sol paneldeki ekran listesi hangi ekranda kaç key'in yazıldığını gösterir;
bir ekrana tıklamak hem maketi hem de satır listesini oraya götürür. Altındaki
filtreler satır listesini daraltır, panelin dibindeki **Dışa aktar** menüsü
bütün çıktıları toplar.

Metin modu (aşağıdaki üç seçenek) her iki görünümde de geçerlidir.

## Metin modları

Önizlemenin üstündeki anahtar makette hangi metnin görüneceğini belirler:

- **Tasarım metni** — tasarımcının koyduğu dummy metin (`content.json` içindeki `dummy`).
- **Final metin** — yazılan metin. Henüz yazılmamış alanlar soluk italik durur,
  yani ekran boşalmaz, eksik olan hemen görünür. Metin karakter limitini aşarsa
  makette kırmızı çerçeve çıkar.
- **Key** — her metnin yerinde remote config key'i. Yazılım ekibi "bu yazı hangi key?"
  sorusunu tek bakışta yanıtlar. Akış görünümünde bu mod, tüm ekranların key haritasını
  tek sayfada verir.

Makette bir metne tıklamak aşağıdaki satırına atlar; bir satıra tıklamak makette
o alanı işaretler.

## Otomatik kontroller

Her satır yazıldıkça denetlenir:

- **Karakter limiti** — limit tasarımcı tarafından belirlenir: metnin tasarımı
  bozmadan alabileceği en uzun hâl. Aşınca sayaç ve makette çerçeve kırmızıya döner.
- **Değişken tutarlılığı** — dummy metinde `{price}` varsa final metinde de olmalı.
  Eksik değişken hata, tasarımda olmayan değişken uyarı olarak işaretlenir.
- **Yazım** — uzun tire (—), art arda boşluk, baştaki/sondaki boşluk uyarısı.
  Buton tipindeki key'lerde satır sonu hatadır.

## Çıktılar

| Düğme | İçerik | Kime |
|---|---|---|
| `keys.csv` | Sadece **onaylı** satırlar, iki kolon: `key,value` | Yazılım, remote config'e basmak için |
| `tümü.csv` | Yazılmış tüm satırlar, `key,value` | Erken entegrasyon |
| `full.csv` | Tüm kolonlar (ekran, tip, limit, dummy, final, statü, yazan, not) | Gözden geçirme, arşiv |
| `TSV kopyala` | Panoya TSV | Google Sheets'e yapıştırmak için |
| `JSON indir` | `values.json` anlık görüntüsü | Repoya işlenecek yedek |

## Yeni proje eklemek

```
copy/projects/<slug>/
├── content.json          # key manifesti + akış tanımı
└── screens/
    ├── shared.css        # maketlerin ortak stili (isteğe bağlı)
    ├── s-01.html         # ekran maketi
    └── s-02.html
```

Sonra `copy/projects/index.json` içine bir satır ekleyin. Başka adım yok,
statik dosya; `main`'e push edilince canlıya çıkar.

### content.json

```jsonc
{
  "slug": "hard-paywall",
  "name": "Hard Paywall",
  "accent": "#5B3DF5",
  "prefix": "paywall.hard",
  "styles": "screens/shared.css",   // bir kez yüklenir, tüm maketler kullanır
  "screens": [
    { "id": "S-01", "name": "Paywall", "file": "screens/s-01.html", "note": "..." }
  ],
  "flow": {
    "dir": "LR",
    "scale": 0.35,
    "lanes": [
      { "id": "main", "label": "Ana yol" },
      { "id": "err",  "label": "Hata durumları" }
    ],
    "nodes": [
      { "id": "start", "kind": "terminal", "label": "Uygulama açılışı", "lane": "main", "step": 1 },
      { "id": "S-01",  "kind": "screen",   "lane": "main", "step": 2 },
      { "id": "S-03",  "kind": "screen",   "lane": "err",  "step": 3 }
    ],
    "edges": [
      { "from": "start", "to": "S-01", "label": "abonelik yok" },
      { "from": "S-03",  "to": "S-01", "label": "purchase_error.cta", "back": true }
    ]
  },
  "keys": [
    {
      "key": "paywall.hard.title",   // yazılım belirler
      "screen": "S-01",
      "type": "başlık",              // rozet · başlık · gövde · buton · liste satırı · fiyat · yasal · durum
      "limit": 34,                   // tasarımın taşımadan kaldırdığı karakter
      "context": "Ana vaat. İki satırı geçerse tasarım bozulur.",
      "dummy": "Tüm özellikleri aç"  // tasarımdaki metin
    }
  ]
}
```

### Akış tanımı

**Kulvarlı soldan sağa** (`"dir": "LR"`, önerilen). `lanes` yatay şeritleri
yukarıdan aşağı sırayla tanımlar; ilki ana yoldur ve vurgulu zeminle çizilir.
Her düğüm `lane` (hangi şerit) ve `step` (soldan kaçıncı sütun) alır. Aynı
şeritte aynı adımda iki düğüm olamaz. `scale` akışın açılış yakınlaştırmasıdır
(0.25 - 1.0) ve aynı zamanda **Sıkı** ölçeğidir; **Rahat** bunun 15 puan üstünü
kullanır.

Yerleştirirken iki kural işi kolaylaştırır: ana yolu tek şeritte kesintisiz
tut, bir sapmayı geldiği ekranın adımına koy (dallanma dik iner, çapraz
uzamaz).

**Dikey ızgara** (`dir` verilmezse). Düğümler `col` ve `row` ile yerleşir,
kulvar yoktur. Küçük akışlarda yeterli.

`kind` ya `screen` (o zaman `id` bir ekran kimliğidir) ya da `terminal`
(akışın girişi ve çıkışı gibi ekran olmayan uçlar; metni `label` ile verilir).

`flow.edges` geçişleri tanımlar. `label` geçişi tetikleyen şeydir; key adı
yazarsanız (`cta.primary`) mono ve renkli görünür, düz cümle yazarsanız
(`ödeme reddedildi`) normal metin olur. Yukarı doğru giden dönüşlerde
`"back": true` ekleyin: LR düzeninde ok kesikli çizilir ve en alttaki dönüş
şeridinden dolanır, dikey ızgarada yandan dolanır (`"side": "left" | "right"`).

Etiketler oklar arasındaki boşluğa oturur, o yüzden kısa tutun: "abonelik yok"
iyi, "kullanıcının aktif aboneliği yok" taşar.

Akış tanımı yoksa görünüm boş kalır, Tablo görünümü etkilenmez.

### Ekran maketi

Sıradan bir HTML parçası. Metin taşıyan her düğüme `data-copy="<key>"` yazın,
gerisini atölye halleder:

```html
<h1 data-copy="paywall.hard.title">Tüm özellikleri aç</h1>
```

Maket 320px genişliğinde ve sabit yükseklikte olmalı (pilotta 668px), yoksa akış
görünümündeki telefonlar birbirini tutmaz. Stil `styles` ile verilen ortak dosyada
ve tek bir kök sınıfın altında dursun (`.scr { ... }`), sayfanın geri kalanına sızmasın.
Ortak dosya sayfaya olduğu gibi yüklendiği için kök sınıf projeye özel ve yeterince
tuhaf olmalı; atölyenin kendi sınıflarıyla (`row`, `chip`, `flt`, `srow`, `card`…)
çakışan bir ad arayüzü bozar.

### Key adlandırma

`<ürün>.<ekran>.<eleman>` · küçük harf, nokta ile ayrılmış, Türkçe karaktersiz.

```
paywall.hard.title
paywall.hard.plan.annual.price
paywall.hard.state.purchase_error.body
```

Ekranda görünmeyen durum metinleri `state.` altında toplanır.

## Figma bağlantısı

Figma'daki metin katmanını key ile adlandırın (`$paywall.hard.title`). O zaman
Figma dosyası bir kez bağlandığında ekran maketleri ve dummy metinler dosyadan
üretilebilir; tasarım değişince key listesi elle güncellenmez. `content.json`
içindeki `figma` alanı bunun için ayrıldı.

Bu adım şu an manuel: maketler elle yazılıyor.

## Lokalde çalıştırma

Repoyu klonlayıp branch'e geç:

```bash
git clone https://github.com/ohtufan/4pflows.git
cd 4pflows
git checkout claude/whatsapp-design-content-workflow-5ibrm5
```

**Sadece arayüze bakmak için** herhangi bir statik sunucu yeter:

```bash
python3 -m http.server 8788
# http://localhost:8788/copy/?p=hard-paywall
```

Bu modda `/api/copy/...` yanıt vermez, atölye sarı uyarıyı gösterip yazılanları
tarayıcıda tutar. Tablo, akış, metin modları, CSV çıkışı hepsi çalışır.

**Kayıt dahil her şeyi denemek için** Cloudflare'in kendi dev sunucusu:

```bash
npx wrangler pages dev . --kv COPY_KV --port 8790
# http://localhost:8790/copy/?p=hard-paywall
```

`--kv COPY_KV` yerelde geçici bir KV açar, Function gerçek ortamdaki gibi çalışır;
gösterge "bağlı" der. Veri `.wrangler/state` altında tutulur, silince sıfırlanır.
Gerçek (uzak) KV'ye bağlanmak gerekmez, gerekmemeli de: production verisiyle
oynamamak için yerel KV yeterli.

## Kayıt: Cloudflare KV kurulumu

Yazılan metinler `/api/copy/<slug>` üzerinden Workers KV'ye kaydedilir
(`functions/api/copy/[slug].js`). Binding tanımlı değilse arayüz sarı bir uyarı
gösterir ve yazılanlar yalnızca o tarayıcıda tutulur.

Bir kerelik kurulum:

1. Cloudflare panelinde **Workers & Pages › KV › Create namespace**, adı örneğin `4pflows-copy`.
2. Pages projesinde **Settings › Functions › KV namespace bindings › Add binding**
   - Variable name: `COPY_KV`
   - KV namespace: az önce açtığınız namespace
   - Production ve Preview için ayrı ayrı ekleyin.
3. Yeniden deploy edin.

Bağlantı kurulduğunda üst sağdaki gösterge "bağlı" der ve her değişiklikten
sonra "kaydedildi HH:MM" olur.

Notlar:

- Çevrimdışıyken yazılanlar tarayıcıda tutulur, bağlantı gelince otomatik gönderilir.
- Sayfa 20 saniyede bir sunucuyu yoklar, başkasının yazdığı satırlar kendiliğinden düşer.
  O anda imlecin içinde olduğu satıra dokunulmaz.
- KV bölgeler arası eventual consistent'tır; aynı satırı iki kişi aynı anda yazarsa
  son yazan kazanır. Pratikte herkes kendi satırını yazdığı için sorun çıkarmaz.
- Link'i olan herkes yazabilir. Site `noindex` ve dahili; erişim kısıtı gerekirse
  Cloudflare Access ile `/copy/*` yoluna kural eklenebilir.

## API

| | |
|---|---|
| `GET /api/copy/<slug>` | `{ok, storage, doc:{rev, updated_at, values}}` |
| `PATCH /api/copy/<slug>` | `{key, patch:{final?, status?, note?}, editor}` → tek satırı birleştirir |
| `PUT /api/copy/<slug>` | `{values, editor}` → tüm tabloyu değiştirir (geri yükleme) |

Statüler: `taslak` · `yazıldı` · `revize` · `onaylı`.
