# İç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ı: `flows.4plabs.com/copy/` · Örnek proje: `?p=eposta-dogrulama`

## 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": "eposta-dogrulama",
  "name": "E-posta Ekleme ve Doğrulama",
  "accent": "#AB6400",
  "prefix": "email",
  "device": { "w": 390, "h": 844 },  // maket ölçüsü; yoksa 320x668
  "styles": "screens/shared.css",    // bir kez yüklenir, tüm maketler kullanır
  "screens": [
    { "id": "S-02", "name": "E-postanı Ekle", "file": "screens/s-02.html", "note": "..." },
    { "id": "E-01", "name": "Doğrulama e-postası", "file": "screens/e-01.html",
      "device": { "w": 600, "h": 701 } }        // bu ekran projeninkinden farklı ölçüde
  ],
  "flow": {
    "dir": "LR",
    "scale": 0.3,
    "lanes": [
      { "id": "main", "label": "Ana yol" },
      { "id": "alt",  "label": "Diğer durumlar" }
    ],
    "nodes": [
      { "id": "f-start", "kind": "terminal", "label": "Profil · Düzenle", "lane": "main", "step": 1 },
      { "id": "S-02",    "lane": "main", "step": 4 },
      { "id": "S-03",    "lane": "alt",  "step": 4 }
    ],
    "edges": [
      { "from": "f-start", "to": "S-02", "label": "E-posta" },
      { "from": "S-06",    "to": "f-valid", "label": "Bağlantı Gönder", "back": true }
    ]
  },
  "keys": [
    {
      "key": "email.add.title",      // yazılım belirler
      "screen": "S-02",
      "type": "başlık",              // rozet · başlık · gövde · buton · etiket · dipnot · durum
      "limit": 28,                   // tasarımın taşımadan kaldırdığı karakter
      "context": "Modalın başlığı. Tek satır.",
      "dummy": "E-postanı Ekle"      // 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).

`"back": true` olan ok kesikli çizilir ve dolanarak döner: iki ucu aynı
şeritteyse o şeridin dibinden, farklı şeritlerdeyse en alttaki ortak
şeritten geçer. Şerit için gereken boşluk kendiliğinden açılır.

**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="email.add.title">E-postanı Ekle</h1>
```

Bir projenin bütün maketleri aynı ölçüde ve sabit olmalı, yoksa akış görünümündeki
telefonlar birbirini tutmaz. Ölçüyü `content.json` içindeki `device` belirler:

```json
"device": { "w": 390, "h": 844 }
```

Alan yoksa 320x668 varsayılır. Değer CSS değişkenine yazılır; önizleme sütunu,
telefon çerçevesi ve akıştaki ölçekleme bunu kullanır.

Bir ekran diğerlerinden farklı ölçüdeyse (e-posta şablonu, tablet, masaüstü)
kendi `device` alanını alır ve projeninkini geçersiz kılar:

```json
{ "id": "E-01", "name": "Doğrulama e-postası", "file": "screens/e-01.html",
  "device": { "w": 600, "h": 701 } }
```

Önizleme sütunu o an açık ekranın genişliğine göre kurulur, akışta her düğüm
kendi ölçüsünde çizilir.

### Birden fazla dil

Proje birden fazla dilde yazılacaksa `langs` eklenir. İlk dil ana dildir:

```json
"langs": [
  { "id": "tr", "label": "TR" },
  { "id": "en", "label": "EN" }
]
```

Her satırda dil başına bir metin kutusu çıkar; karakter limiti, değişken
tutarlılığı ve diğer kontroller her dil için ayrı çalışır, ana dil yazılıp
diğeri boş kalırsa satır uyarı verir. Önizlemedeki **Final** düğmesi dile
göre ikiye ayrılır, yani makette İngilizce metni de görebilirsin.

Tasarımın ikinci dilde kendi metni varsa key'e `dummy_<id>` eklenir; o metin
satırda ilgili kutunun yanında görünür ve değişken kontrolü onu esas alır.
Aynı ekranın iki dili için ayrı maket koymak gerekmez: **Final** düğmesi
zaten dile göre ayrıldığı için tek maket iki dili de gösterir. Yazılmamış bir
alan o dilin tasarım metnine düşer, yani İngilizce moddayken Türkçe metin
görünmez.

Bir maket her koşulda tek bir dilde duracaksa ekrana `lang` verilir; o maket
hangi mod seçili olursa olsun kendi dilinde çizilir:

```json
{ "id": "X-01", "name": "...", "file": "screens/x-01.html", "lang": "en" }
```

Alan yoksa proje tek dillidir ve hiçbir şey değişmez.

`keys.csv` ve `tümü.csv` çok dilli projede `key,tr,en` sütunlarıyla çıkar
(tek dilde `key,value` olarak kalır); `full.csv` her dil için metin ve
uzunluk sütunu verir. Kayıtta ana dil `final`, diğerleri `final_<id>`
alanında durur.

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 (`btn`, `row`, `chip`, `flt`, `srow`, `card`, `modes`, `search`,
`intro`, `note`…) çakışan adlar iki yönde de sızar: maket bozulur ya da arayüz
bozulur. Maket içinde `btn` yerine `cta` gibi projeye özel adlar kullan.

Bir key birden fazla makette geçebilir. Aynı ekranın iki durumunu (örneğin onay
kutusu boş / işaretli) iki ayrı ekran olarak koyduğunda ikinci maket birincinin
`data-copy` key'lerini kullanır; metin bir kez yazılır, iki maket birden güncellenir.

### Key adlandırma

`<ürün>.<ekran>.<eleman>` · küçük harf, nokta ile ayrılmış, Türkçe karaktersiz.
Key'i yazılım ekibi belirler; remote config'de hangi ad kullanılıyorsa atölyeye
o yazılır. Alt çizgili adlar (`profile_avatar_popup_title`) da geçerlidir,
önemli olan iki yerde birebir aynı olması.

```
email.add.title
email.expired.cta.resend
email.add.state.in_use.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 (`$email.add.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=eposta-dogrulama
```

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=eposta-dogrulama
```

`--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ı`.
