Mấy tháng trước mình ship app iOS của DIALØGUE như một bản port của sản phẩm web. Rồi mình dựng lại gần như toàn bộ nó thành một app native.
App giờ đã live trên App Store — nếu bạn tò mò thì cứ vào nghịch thử. Phần còn lại của bài này là chuyện gì đã thay đổi, và vì sao.
Nghe thì tưởng chỉ là chuyện chữ nghĩa, cho đến khi bạn cầm hai phiên bản trên tay. Bản đầu là web app thu nhỏ lại cho vừa cái điện thoại: cùng những tab đó, cùng cái wizard tạo podcast đó, cùng cái dashboard đó. Nó compile được, chạy được, qua review được. Và nó có cảm giác như một cái website đang mặc bộ đồ hoá trang thành app.
Một web app thu nhỏ lại cho vừa điện thoại thì vẫn là một web app. Native không phải lớp sơn phủ lên layout web của bạn — nó là một hợp đồng khác với thiết bị. Cái hợp đồng đó chính là thứ mình đã bỏ qua, và dựng lại để tôn trọng nó đã thay đổi các tab, phần audio, bề mặt nghe, hành vi offline, và cả cách app khởi động. Đây là ý nghĩa thật sự của chuyện đó, từng màn hình một.
Bản port là một cái dashboard
Phiên bản đầu tiên mặc định rằng việc của app là phơi bày cái máy generate ra ngoài. DIALØGUE tạo podcast bằng AI, nên suy nghĩ lúc đó là: nhét hết mọi bề mặt web lên màn hình là xong.
Cái bạn nhận được từ đó là một dashboard. Năm tab, một wizard nhiều bước, panel cho đủ thứ. Không ai mở một app podcast lên để vận hành một dashboard. Người ta mở nó lên để biến một ý tưởng thành âm thanh, để nghe lại thứ mình vừa tạo ra, hoặc để bắt đầu một show họ từng làm trước đó. Giao diện được port qua bảo vệ cái layout của web app. Nó không bảo vệ ba việc đó.
Nên bản dựng lại bắt đầu từ chính thứ đầu tiên bạn nhìn thấy.
Ba tab, vì cái điện thoại trừng phạt việc lạm dụng tab
App cũ mở lên là năm tab: Library, Studio, Create, Credits, Profile. Đó là thứ bạn nhận được khi port web app qua — mỗi bề mặt web lại được phong cho một cái tab.
App dựng lại có ba: Listen, Create, You.
Hai tab không sống sót. Credits thôi không còn là một điểm đến — không ai mở app lên để ngồi nhìn số dư. Nó chuyển vào trong "You", và đúng khoảnh khắc thật sự quan trọng — sắp hết credit ngay lúc bạn định generate — giờ bật ra một sheet mua hàng ngay tại chỗ đó. Studio thôi không còn là một nơi chốn. Nó trở thành Series — một thiết lập đã lưu (host, tông giọng, format, ngôn ngữ, mẫu nguồn) sống bên trong Create và xuất hiện trong Listen như một bộ sưu tập, thay vì một cái tab giả vờ làm phòng điều khiển. Library và Profile thì thành "Listen" và "You" cho mộc mạc.
Trên desktop, thêm một tab là miễn phí. Trên điện thoại, mỗi cái tab là một loại thuế đánh vào sự chú ý. Bản dựng lại đã trả bớt khoản thuế đó.

Audio theo đúng cách iOS mong đợi
Đây là chỗ "native là một hợp đồng" thôi không còn trừu tượng nữa.
App được port qua không phải mù tịt về audio. Nó đã hiện trên màn hình khoá với nút play, pause, skip, nó dừng lại khi có cuộc gọi, và nó tắt khi bạn rút tai nghe ra. Đó là mức tối thiểu, và những thứ đó đều có.
Cái nó không làm được là cư xử như một app audio thật sự một khi màn hình đã khoá. Bạn không thể kéo thanh tua từ màn hình khoá. Không có ảnh bìa ở đó — chỉ có chữ. Không có nút AirPlay, không có hẹn giờ ngủ, và skip thì cố định mười lăm giây dù bạn thích hay không.
Bản dựng lại khép lại khoảng cách đó cùng với nền tảng, chứ không phải lách quanh nó. Màn hình khoá giờ mang ảnh bìa tập động và một thanh tua bạn có thể kéo đến bất kỳ điểm nào. Có AirPlay, có hẹn giờ ngủ làm âm lượng mờ dần đi thay vì cắt phụp một cái, và một khoảng skip bạn tự đặt từ mười đến sáu mươi giây — rồi giá trị đó cũng điều khiển luôn các nút trên màn hình khoá, vì nút của hệ thống thì phải khớp với app. Audio session tự khai báo mình là spoken audio, để hệ điều hành đối xử với nó như tiếng nói chứ không phải nhạc.
Chẳng có gì trong đám này là hào nhoáng cả. Đó mới đúng là điểm chính. Trên điện thoại, audio mà phớt lờ màn hình khoá và AirPlay thì không phải là "tối giản". Nó hỏng đúng vào những khoảnh khắc người ta thật sự nghe — đang đi bộ, đang lái xe, điện thoại nằm trong túi.
Một transcript mà chỉ podcast AI mới dựng được
Đây là phần thật sự là của riêng tụi mình, chứ không chỉ là vệ sinh iOS tốt.
Trong khi một tập đang phát, app hiện một transcript đồng bộ: dòng đang nói được highlight, khung tự cuộn để giữ nó ở giữa, và bạn chạm vào dòng nào là nhảy thẳng đến đúng khoảnh khắc đó. Một app podcast thông thường không thật sự làm được điều này, vì nó không biết câu nào được nói ra vào lúc nào. DIALØGUE thì biết — nó tạo ra cái script, nên nó đã biết sẵn cấu trúc của tập.
Chi tiết kỹ thuật thành thật là: một dòng chỉ chạm được khi audio có timing chính xác đến từng đoạn. Khi timing chỉ là gần đúng, dòng vẫn hiện ra, chỉ là không tua đến được, và app không giả vờ ngược lại. Cùng cái timing đoạn đó vẽ luôn các dấu chương ngay trên thanh tua, nên bạn có thể lướt qua một tập như nghe audio nhưng có sẵn tấm bản đồ về thứ hệ thống đã làm. Transcript còn cho biết host nào nói dòng nào, vì script được viết dưới dạng đối thoại hai host.
Đây chính là khác biệt giữa việc bắt vít một cái transcript lên trên một player, và việc coi cái script được generate ra là nguồn chân lý cho toàn bộ trải nghiệm nghe.
Có hai thứ làm nên điều đó, và cả hai đều không hiện ra trên màn hình. Backend đóng dấu thời gian bắt đầu và kết thúc cho từng đoạn, và đánh dấu liệu timing là chính xác hay chỉ ước lượng — để app có thể cho bạn chạm vào một dòng có timing chính xác và lặng lẽ từ chối làm giả trên một dòng chỉ ước lượng. Và vị trí phát của bạn được lưu trên server, không chỉ trên thiết bị, nên "nghe tiếp từ chỗ bỏ dở" vẫn đúng kể cả khi bạn bắt đầu một tập trên web rồi nghe nốt trên điện thoại.
Offline sống sót được một chuyến đi làm thật sự
App đã có thể download tập từ những ngày đầu. Đó không phải cái mới, và mình muốn nói cho chính xác. Cái bản dựng lại thêm vào là sự bền bỉ — khác biệt giữa một tính năng download và một cái offline mà bạn có thể tin cậy.
Một lượt download bị gián đoạn giờ tiếp tục lại từ chỗ nó dừng thay vì làm lại từ đầu, vì app lưu lại resume data của hệ thống cho từng tập và khởi động lại từ đó. Có một tuỳ chọn chỉ-Wi-Fi cấm cellular thật sự, nên một lượt download đang xếp hàng sẽ đợi Wi-Fi thay vì âm thầm đốt gói data của bạn. Download chạy tối đa ba cái một lúc trong một hàng đợi FIFO thay vì giẫm đạp lên mạng. Có một màn hình lưu trữ để bạn xem và xoá những gì đang nằm trên máy. Còn những lượt fetch thoáng qua bị fail trên kết nối chập chờn thì retry kèm backoff — ba lần thử, từ nửa giây tăng dần đến một mức trần, không bao giờ retry khi chính người dùng đã hủy.
Retry thì dễ; retry mà không cãi nhau với một người dùng vừa bấm hủy mới là phần làm cho offline có cảm giác chắc chắn trên một kết nối tàu điện ngầm tệ hại, thay vì cứ quay vòng vòng.

Tức thì, vì một cái spinner đọc ra là "hỏng"
Trên điện thoại, độ trễ là một cảm giác, không phải một con số. Một cái spinner lúc khởi động lạnh đọc ra là "app này hỏng rồi", kể cả khi chẳng có gì sai.
App được port qua download lại từng ảnh bìa mỗi lần khởi động lạnh, nên thư viện mở ra là một bức tường spinner. Bản dựng lại thêm một cache chung cho ảnh bìa, vừa trên memory vừa trên disk — lớp disk sống sót qua các lần khởi động lại, lớp memory giữ cho việc cuộn được mượt, và màn hình khoá tái sử dụng đúng cái cache đó cho ảnh bìa của nó. Mở lại một tập trước đây phải đợi một vòng round trip qua mạng; giờ các đoạn và transcript được cache theo từng tập và render ngay lập tức, rồi lặng lẽ refresh ở background. Mình cũng kéo cái tick phát-mỗi-nửa-giây ra khỏi các dòng trong list, để cái timer thôi bắt cả thư viện phải vẽ lại.
Đây không phải tính năng bạn chụp màn hình được. Nó là cái khoảng cách giữa một app và một website.
Nó báo cho bạn khi tập đã sẵn sàng
Generate một podcast mất vài phút, không phải vài giây — có nghiên cứu, một dàn ý, một script, rồi mới đến audio. App được port qua bắt bạn ngồi nhìn một thanh tiến trình suốt cả thời gian đó. App native thì không.
Với sự cho phép của bạn, nó gửi một push notification đúng khoảnh khắc tập của bạn sẵn sàng, nên bạn có thể khoá máy, đi làm việc khác, rồi quay lại khi nó rung. Device token được lưu phía server, chỉ cái job gửi notification mới đọc được nó, và bạn có thể tắt toàn bộ thứ này trong settings. Đó là một mẩu đường ống nhỏ — một cái bảng, một worker, dịch vụ push của Apple — nhưng nó đổi hình dáng cảm nhận của sản phẩm từ "ngồi đợi ở màn hình này" thành "khi nào xong tôi báo cho".
Workflow có thể bắt đầu từ ngoài app
Một app native không chỉ sống bên trong cửa sổ của riêng nó. Bản dựng lại thêm hỗ trợ Siri và Shortcuts qua App Intents, nên "tạo một podcast", "nghe tiếp", và "mở podcast của tôi" hoạt động như những câu nói, trong app Shortcuts, và trong Spotlight — không cần entitlement đặc biệt nào. Quyết định "nghe tiếp" nên làm gì (phát tiếp tập hiện tại, hay mở thư viện nếu chưa có gì đang nạp) là một hàm thuần nhỏ xíu mà mình unit-test riêng được — đúng kiểu thứ giữ cho hành vi của Siri không trôi dạt lung tung.
Còn có một màn welcome ba trang giản dị ở lần chạy đầu — create, voices, nghe ở mọi nơi — chỉ hiện một lần, và các deep link để một link được chạm vào sẽ mở đúng màn hình thay vì quăng bạn về tab home. Toàn thứ nhỏ. Chúng chính là khác biệt giữa một app chỉ nằm trên điện thoại và một app thật sự thuộc về cái điện thoại đó.
Nói một câu về chỗ mình dừng lại: chưa có widget màn hình chính, chưa có Live Activities, và chưa có CarPlay — mỗi thứ cần extension riêng hoặc một entitlement do Apple cấp, và mình chọn ship phần lõi nghe trước. "Native" ở đây là một hướng đi, không phải một checklist đã hoàn tất.
Bài học mình rút ra từ chuyện này
Nếu bạn đang port một thứ gì đó sang nền tảng mới, nước đi hấp dẫn là làm cho nó chạy được trên đó rồi coi như xong. Nó sẽ chạy. Nhưng nó cũng sẽ có cảm giác như đồ đi mượn.
Native là một hợp đồng với thiết bị: tôn trọng màn hình khoá, việc đổi thiết bị phát audio, thực tế offline, những bề mặt hệ thống mà người ta vốn đã dùng. Một bản port tôn trọng cái layout cũ của bạn. Một app native tôn trọng các quy ước của nền tảng, kể cả khi điều đó nghĩa là xoá bớt tab và viết lại những màn hình bạn từng ship.
Mình không có con số cài đặt hay giữ chân nào để khoe — phiên bản hiện tại đang live trên App Store, và đó là tình trạng thật thà của nó. Bài kiểm tra thật sự chưa bao giờ là mấy tấm screenshot trông có native hay không. Nó là chuyện liệu một người có tạo ra một tập, nghe nó trong một buổi đi bộ với điện thoại đã khoá, rồi quay lại tạo thêm một tập nữa hay không.
Nếu bạn build cho mobile, mình rất tò mò bạn vạch ranh giới ở đâu: khi nào "nó chạy được trên điện thoại" là đủ, và khi nào cái hợp đồng của nền tảng buộc bạn phải dựng lại?
Câu hỏi thường gặp
Bản dựng lại app iOS của DIALØGUE đã thay đổi những gì?
App đi từ một bản port của sản phẩm web thành một bản dựng lại native. Kiến trúc thông tin rút từ năm tab xuống còn ba (Listen, Create, You). Trải nghiệm nghe có thêm transcript đồng bộ chạm-để-tua, dấu chương, tua và ảnh bìa trên màn hình khoá, AirPlay, hẹn giờ ngủ, và khoảng skip tuỳ chỉnh được. Download offline trở nên bền bỉ, app cache ảnh bìa và các đoạn để có cảm giác tức thì, Siri/Shortcuts cho phép các thao tác lõi bắt đầu từ ngoài app, và một push notification báo cho bạn khi một tập đã sẵn sàng.
Vì sao năm tab thành ba?
Vì điện thoại trừng phạt mỗi cái tab thừa. Credits không phải nơi ai muốn ghé, nên nó gập vào trong "You" cộng với một sheet mua hàng hiện ra đúng lúc bạn sắp hết. Studio thành Series — một thiết lập đã lưu nằm trong Create chứ không phải một tab riêng. Library và Profile thành "Listen" và "You" cho mộc mạc.
Bản dựng lại thêm những tính năng iOS native nào?
Tua và ảnh bìa động trên màn hình khoá, AirPlay, hẹn giờ ngủ với hiệu ứng mờ âm lượng, khoảng skip tuỳ chỉnh (10–60 giây) cũng điều khiển luôn các nút trên màn hình khoá, dấu chương trên thanh tua, transcript đồng bộ chạm-để-tua, download offline bền bỉ (tiếp tục, chỉ-Wi-Fi, quản lý lưu trữ, hàng đợi có giới hạn), cache chung cho ảnh bìa và các đoạn để render tức thì, các intent Siri/Shortcuts, push notification "tập đã sẵn sàng", vị trí phát lưu phía server nghe tiếp được qua cả web lẫn điện thoại, deep link, và onboarding lần chạy đầu.
Transcript đồng bộ có hoạt động cho mọi tập không?
Một dòng transcript chỉ chạm-để-tua được khi audio có timing chính xác đến từng đoạn. Khi timing chỉ gần đúng, dòng vẫn hiển thị nhưng không tua đến được, và app không giả vờ ngược lại. Transcript làm được là vì DIALØGUE tự tạo ra script, nên nó biết cấu trúc và biết ai nói câu nào.
Studio và Series khác nhau ở đâu?
Studio ngụ ý một phòng điều khiển đầy nút vặn. Series chỉ là một thiết lập đã lưu — host, tông giọng, format, ngôn ngữ, và mẫu nguồn — cho bạn bắt đầu tập kế tiếp mà không phải cấu hình lại tất cả. Nó là một preset bên trong Create, không phải một tab độc lập.
Cái này khác audio overview của NotebookLM thế nào?
NotebookLM thật sự hữu ích, và miễn phí, cho việc biến các nguồn của bạn thành một bản audio overview nhanh. DIALØGUE thì đang cố trở thành một sản phẩm nghe native trọn vẹn nằm trên nền generate: review dàn ý và script trước khi có bất kỳ audio nào, chọn giọng, transcript đồng bộ chạm-để-tua, các chương, điều khiển trên màn hình khoá và AirPlay, download offline, Siri, và Series cho các show làm đi làm lại. Khác biệt thật thà ít nằm ở chuyện "ai generate audio hay hơn" mà nằm nhiều hơn ở chuyện "điều gì xảy ra với cái tập sau khi nó đã tồn tại".
Bản dựng lại có thêm tính năng nghe offline không?
Download offline thì đã có sẵn. Bản dựng lại làm nó bền bỉ: download bị gián đoạn thì tiếp tục thay vì làm lại từ đầu, có tuỳ chọn chỉ-Wi-Fi và một màn hình lưu trữ, download chạy tối đa ba cái một lúc, và lỗi mạng thoáng qua thì retry kèm backoff mà không cãi nhau với người dùng vừa bấm hủy.
Thôi, từ mình thì tạm vậy đã.
Thân, Chandler
