2026-03-05 14:12:40
Flashback é um aplicativo web que simula a experiência de usar uma câmera instantânea retrô (Polaroid).
O projeto vem dos usuários vietnamitas do TRAE, The Vibe Coders (membros: Mai Nguyen & Son Le), ambos desenvolvedores backend. Durante a experiência TRAE Vietnam Vibe Coding em 22/11, eles criaram este aplicativo web usando o TRAE SOLO. A câmera transforma uma webcam comum em uma Polaroid virtual retrô, recriando fielmente todo o processo, desde o clique do obturador até a revelação da foto. Os usuários só precisam de um dispositivo com câmera para capturar fotos com filtros vintage e acompanhar a "revelação" das imagens por meio de animações, evocando o charme da era do filme fotográfico.
No início, tudo surgiu da inspiração trazida pela câmera Polaroid de um amigo. Um dos membros da equipe comentou:
“Temos um amigo que possui uma verdadeira Polaroid retrô, e adoramos o processo e a sensação de tirar fotos com ela. Mas a realidade é que essas câmeras não são baratas, e nós não podíamos comprar uma. Então decidimos fazer a nossa própria com código.”
E assim nasceu o Flashback. Ele não apenas resolve a barreira de custo de equipamentos caros, como também oferece os seguintes valores:
Fotografia retrô sem barreiras: qualquer pessoa com uma câmera pode experimentar a diversão de uma Polaroid de forma gratuita.
Interação realista: o usuário precisa ligar a câmera, enquadrar e apertar o obturador, como se estivesse usando uma câmera física.
Processo de revelação fiel: a foto passa de uma tela preta para uma imagem clara em apenas 3 segundos, proporcionando a sensação ritualística da espera.
Valor emocional: cada clique e revelação lenta torna a foto mais memorável do que filtros gerados automaticamente.
Estética retrô preservada: diversos filtros dão às fotos a aparência de “fotos antigas”.
Modo SOLO do TRAE IDE: desenvolvimento assistido por IA, ajudando a criar protótipos rapidamente, gerar código e resolver problemas durante o desenvolvimento.
Deploy com um clique no Vercel: simplificação do fluxo de implantação, permitindo que o app fosse rapidamente publicado.
O Flashback é uma aplicação de página única (SPA) totalmente front-end, com uma stack leve:
Permissão: o usuário clica no botão de energia e autoriza o uso da câmera.
Pré-visualização em tempo real: o vídeo é exibido ao vivo, com o filtro selecionado aplicado.
Captura da imagem: ao clicar no obturador, o quadro atual é capturado no canvas.
Aplicação de filtro: a imagem recebe o filtro escolhido e é processada.
Animação de revelação: a foto “salta” na tela com animação e gradualmente se revela.
Coleção de fotos: o usuário pode arrastar a foto para um mural de recordações.
Acesse a demo online: https://flashbackcamera.vercel.app/
Animação de ligar a câmera e pré-visualização em tempo real
Quatro filtros retrô: Normal, Kodak Color, Matte Faded e Filme 8mm
Animação de “pulo” e revelação das fotos
Mural de fotos arrastáveis
Responsivo para dispositivos móveis
1.Permissões de câmera entre navegadores
Desafio: Chrome, Firefox e Safari tratam permissões de mídia de formas diferentes, dificultando uma experiência uniforme.
Solução: testes extensivos em diferentes navegadores e implementação de múltiplas soluções alternativas para compatibilidade.
2.Otimização de filtros em tempo real
Desafio: aplicar filtros em tempo real sobre o vídeo mantendo 60fps exige alto desempenho.
Solução: testes mostraram que filtros CSS são mais eficientes que filtros Canvas para pré-visualização em tempo real; adotou-se a solução CSS.
3.Implementação do recurso de arrastar e soltar
Desafio: funcionar de forma fluida em desktop e mobile, lidando com múltiplos tipos de eventos e prevenindo comportamentos padrão do navegador.
Solução: criação de um sistema de drag-and-drop customizado, unificando a lógica para diferentes dispositivos.
4.Criar sensação autêntica de retrô
Desafio: equilibrar estética retrô com hábitos de uso modernos.
Solução: refinamento contínuo do design da câmera, animações e interações até atingir um equilíbrio adequado.
5.Adaptação de efeitos 3D responsivos
Desafio: efeitos de paralaxe 3D precisam parecer naturais em telas de todos os tamanhos.
Solução: ajuste dos parâmetros 3D conforme o tamanho da tela, garantindo boa experiência visual em qualquer dispositivo.
Experiência do usuário em primeiro lugar: mais importante do que técnicas avançadas de processamento de imagem é transmitir nostalgia e interações suaves.
Efeitos sonoros aumentam a imersão: simples efeitos sonoros elevam significativamente a sensação de realismo.
O Flashback Camera não é apenas um projeto de hobby; é um experimento de “nostalgia digital” e uma demonstração do potencial lúdico da web. A equipe The Vibe Coders usou tecnologias simples para criar uma experiência emocionalmente rica e cativante.
Em um mundo obcecado por “máxima eficiência”, essa pequena ferramenta com ritual e romantismo é justamente o que merece ser lembrado.
Esperamos que, no futuro, eles continuem trazendo mais projetos criativos “com calor humano” usando código.
Mais projetos:
2026-03-05 14:11:55
I was working with a global auto industry leader on their post-sales software platform. The platform had recently launched with seven modules. Each module was built as a microservice.
Communication across services happened primarily through a message streaming broker.
The data flow between services was non-trivial — upstream and downstream dependencies, bidirectional communication patterns, and conditional routing based on context.
When an end user raised a complaint, the support team had to perform initial root cause analysis before escalating to engineering.
The system had too many moving parts for quick intuition based debugging. And more importantly, the mental model of "how everything connects” was concentrated in one person on the support team.
This wasn't a tooling problem.
It was a knowledge distribution problem.
The question became:
Can we codify the debugging intuition of the most experienced support engineer — and make it usable by anyone?
That's where the idea of an operations support AI agent emerged.
But we were careful about one thing:
The goal wasn't to make an agent that "knows everything.”
The goal was to make an agent grounded in the actual architecture of the system.
The complexity wasn't just the number of services.
It was:
• Inter service communication patterns
• Conditional flows
• Bidirectional dependencies
• And multiple layers of state verification (UI, logs, database)
So instead of jumping straight into prompt engineering, we started with context engineering.
We asked:
What does a strong human support engineer actually do when debugging?
The answer was structured, even if it wasn't documented.
And that structure became the foundation of the agent.
The services were distributed across multiple repositories (polyrepo structure). To understand interactions, we first had to bring everything into one workspace.
What we did
• Checked out all service repositories together.
• For each service, we prompted AI to generate upstream and downstream dependency diagrams based on message broker configurations found in the codebase.
• We generated these per service to avoid overloading the model.
• Once individual service documents were created, we asked AI to compile them into a single system-wide data flow diagram using Mermaid (text-based diagram generation).
The result was a consolidated "big-picture" document.
This became foundational context for the agent - not a theoretical architecture diagram, but something derived from the actual codebase configurations.
It allowed the agent to reason about interaction points instead of guessing.
One of the most interesting observations was this:
The most effective early debugging didn't start with logs.
It started with the application UI.
Support engineers used UI screens to:
• Search for domain entities
• Inspect state
• Check timestamps
• Identify where a transaction stopped progressing
So we needed the agent to replicate that behavior.
What we did
• Prompted AI to extract the list of UI screens from the micro-frontend system.
• Prompted separately for each UI module to maintain output quality.
• Generated a structured document listing:
• UI screens
• Available search filters
• Displayed columns/data points
Then we created an index/router document that allowed the agent to:
• Identify which screens correspond to a domain entity
• Suggest navigation paths
• Recommend filters to apply
This transformed the agent from a generic reasoning engine into something application-aware.
When UI-level validation wasn't enough, the fallback was querying the database.
But meaningful DB debugging requires:
• Understanding entity relationships
• Knowing which fields exist
• Writing contextually valid queries
So we:
• Generated ER diagrams for each backend service
• Built a routing index so the agent could load the appropriate ER diagram based on the issue's domain context
Again, the pattern was the same:
Keep context modular.
Allow conditional loading.
Avoid overwhelming the model.
Only after building the context layer did we design the agent itself.
We structured it intentionally.
Role: The agent acts as an Expert Operations Support Engineer.
Task:
For every issue:
This sequence mirrors how experienced support engineers approach triage.
Context References:
The agent explicitly refers to:
• big-picture.md
• ui-screens-index.md
• er-diagrams-index.md
Output Format
Every response includes:
• Problem Understanding
• Overall Impact
• Checklist to Follow
• Interaction Points Identified
• Short Summary
The output was designed to be executable.
This resulted in a PoC custom agent capable of generating structured, domain-aware debugging checklists.
More importantly:
We converted implicit operational knowledge into explicit, structured artifacts.
The support workflow was no longer dependent on a single individual's system intuition.
We did not treat the agent as authoritative.
Every generated checklist was reviewed by the existing support engineers who already performed these tasks manually.
The next phase was clear:
Test it with individuals who had minimal context of the system.
Iteration was always part of the plan.
An operations agent like this should be criticized continuously.
Its usefulness depends entirely on how rigorously it is refined.
Once the checklist is grounded in real architecture, each phase becomes automatable.
Future possibilities we identified:
• Automatically updating UI and ER documents on PR merges
• Garbage collection of outdated context
• Triggering the agent via MCP when a P1 ticket is raised
• Attaching generated checklists directly to support tickets
• Providing read-only search capabilities for domain entities
• Integrating log keyword searches and adapting based on results
• Even raising bug tickets automatically if conditions are met
The autonomy doesn't need to jump to full resolution.
It can increase incrementally - phase by phase — based on trust and accuracy.
The hardest part of AI in operations isn't reasoning.
It's grounding.
Once the system's architecture, UI workflows, and data relationships were codified into structured context, the agent's job became deterministic.
2026-03-05 14:10:47
The more I work with Microsoft 365 Copilot, the less I think about prompts – and the more I think about permissions.
It’s one thing to have an AI that can summarise public docs. It’s a very different thing to plug it into your company’s real data and let people ask whatever they want.
At that point, the question isn’t “Can Copilot answer this?” but:
“Should Copilot answer this – for this user – right now?”
In this post I want to talk about security trimming in the context of M365 Copilot:
I’ll keep it concrete and code-backed, not just policy talk.
When people roll out Copilot in a hurry, the conversation often sounds like this:
Pretty bad.
Most organisations have data that:
Think of:
If your Copilot integration ignores permissions, all of that becomes a “nice” answer to:
That’s not a bug, that’s a breach with extra steps.
So let’s be clear: security trimming is not a nice-to-have in Copilot land, it’s the core of whether you can safely use it on real data at all.
In this context, security trimming means:
Every piece of data used to answer a Copilot question must be filtered according to the current user’s identity and permissions.
There are two layers to think about:
Most of the scary stories I see are from the second category.
On the M365 side, the picture looks roughly like this:
flowchart LR
U[User] --> C[Copilot]
C --> P[Plugin / Agent]
P --> G[Microsoft Graph]
G --> D[(M365 Data)]
subgraph M365
G
D
end
When your plugin/agent calls Graph with a delegated user token:
Security trimming happens at the Graph level. That’s good. You still need to be careful which scopes you request, but you’re not manually filtering file lists in your own code.
The problem starts when people think they get the same behaviour automatically for everything else.
As soon as you build your own HTTP connector or call third-party APIs directly from an agent, you can easily sidestep all that nice trimming.
Typical anti-pattern:
Now imagine a user who normally has no access to CEO documents asking:
If your backend happily queries everything with a technical account and returns it to Copilot, you just built a very friendly internal data exfiltration tool.
The key rule is:
Agents and connectors must treat user identity as a first-class input and enforce access checks before they ever touch external data.
Let’s look at a simple pattern I like to use for external systems.
High-level flow:

Here the Mermaid code:
flowchart LR
U[User] --> C[Copilot]
C --> A[AskExternalData / Agent]
A --> B[Backend]
B --> AUTH[Auth / Directory]
B --> EXT[External System]
EXT --> B
B --> C
Important pieces:
Let’s make that concrete.
Define an API where the agent sends the question plus user context:
// Request payload from Copilot plugin
interface AskExternalRequest {
question: string;
userEmail: string;
projectKey?: string;
}
interface AskExternalResponse {
answer: string;
sources: { title: string; url: string }[];
missingInfo: boolean;
}
// src/secureAsk.ts
import { Request, Response } from 'express';
import { getUserPermissions } from './permissions';
import { searchExternalDocs } from './externalDocs';
import { buildAnswerFromDocs } from './rag';
export async function secureAskHandler(req: Request, res: Response) {
const body = req.body as AskExternalRequest;
if (!body.question || !body.userEmail) {
return res.status(400).json({ error: 'question and userEmail are required' });
}
try {
// 1) Resolve user permissions from your directory/IAM
const userPerms = await getUserPermissions(body.userEmail);
if (!userPerms) {
return res.status(403).json({ error: 'unknown_user' });
}
// 2) Search external docs WITH permissions
const docs = await searchExternalDocs(body.question, userPerms, body.projectKey);
if (docs.length === 0) {
const answer = `I couldn't find any documents you have access to that answer "${body.question}".`;
const response: AskExternalResponse = {
answer,
sources: [],
missingInfo: true
};
return res.json(response);
}
// 3) Build an answer using an LLM
const { answer, usedDocs } = await buildAnswerFromDocs(body.question, docs);
const response: AskExternalResponse = {
answer,
sources: usedDocs.map(d => ({ title: d.title, url: d.url })),
missingInfo: false
};
return res.json(response);
} catch (err) {
console.error('secureAsk error', err);
return res.status(500).json({ error: 'internal_error' });
}
}
You can model permissions in many ways. Here’s a very simple example:
// src/permissions.ts
export type UserContext = {
email: string;
roles: string[]; // e.g. ['EMPLOYEE', 'HR', 'ENGINEERING_MANAGER']
groups: string[]; // e.g. ['project-phoenix', 'dept-engineering']
};
// In reality this might talk to Azure AD, Okta, your own directory, ...
export async function getUserPermissions(email: string): Promise<UserContext | null> {
// Placeholder: load from your IAM
const entry = await directoryLookup(email);
if (!entry) return null;
return {
email,
roles: entry.roles,
groups: entry.groups
};
}
For external documents (Confluence pages, database rows, etc.) you need a way to represent who is allowed to see what.
A very generic model:
export type DocumentAcl = {
allowedRoles?: string[]; // e.g. ['HR', 'CEO']
allowedGroups?: string[]; // e.g. ['project-phoenix']
};
export type ExternalDoc = {
id: string;
title: string;
url: string;
content: string;
acl: DocumentAcl;
};
function hasAccess(doc: ExternalDoc, user: UserContext): boolean {
const { allowedRoles, allowedGroups } = doc.acl;
if (!allowedRoles && !allowedGroups) {
// default: visible to all employees
return true;
}
if (allowedRoles && allowedRoles.some(r => user.roles.includes(r))) {
return true;
}
if (allowedGroups && allowedGroups.some(g => user.groups.includes(g))) {
return true;
}
return false;
}
export function filterDocsByAcl(docs: ExternalDoc[], user: UserContext): ExternalDoc[] {
return docs.filter(doc => hasAccess(doc, user));
}
Then in your search function:
// src/externalDocs.ts
import { ExternalDoc, filterDocsByAcl } from './acl';
import { UserContext } from './permissions';
export async function searchExternalDocs(query: string, user: UserContext, projectKey?: string): Promise<ExternalDoc[]> {
// 1) Query the external system (e.g. Confluence, SQL, custom API)
const rawDocs = await rawSearchDocs(query, projectKey);
// 2) Apply ACL-based filtering
const visibleDocs = filterDocsByAcl(rawDocs, user);
return visibleDocs;
}
High-level, the flow then looks like this:

Let’s connect this back to Confluence, similar to my previous “Ask the Company” post.
Instead of querying Confluence with a technical account and dumping all results into an LLM, we:
In a simple setup, you might maintain a mapping like:
// src/projectSpaces.ts
const projectSpaceMap: Record<string, string> = {
'project-phoenix': 'PHX',
'project-orion': 'ORI'
};
export function getAllowedSpacesForUser(user: UserContext): string[] {
const spaces = new Set<string>();
for (const group of user.groups) {
const spaceKey = projectSpaceMap[group];
if (spaceKey) spaces.add(spaceKey);
}
// Add global "company" space for all employees if you want
spaces.add('COMPANY');
return Array.from(spaces);
}
Then use it in your Confluence search:
import { getAllowedSpacesForUser, UserContext } from './permissions';
export async function searchConfluenceSecure(query: string, user: UserContext): Promise<ConfluencePage[]> {
const spaces = getAllowedSpacesForUser(user);
const cqlParts = [`text ~ "${query.replace(/"/g, '\\"')}"`];
if (spaces.length > 0) {
const spaceFilter = spaces.map(s => `space = "${s}"`).join(' OR ');
cqlParts.push(`(${spaceFilter})`);
}
const cql = cqlParts.join(' AND ');
// ... same as before, but using the restricted CQL
}
Now the agent will:
Even with good trimming, there are categories of data I’d think twice about running through a general Copilot agent at all:
A few strategies that help:
Security trimming is not only about “who can read the file”. It’s also about “which AI flows are allowed to touch it and combine it with other information”.
If I had to put this into a blunt checklist, it would look like this:
Microsoft 365 and Graph do a lot of the heavy lifting for you on the M365 side. If you stay within that world and use delegated permissions correctly, security trimming works in your favour.
The moment you step outside – Confluence, SAP, custom APIs, databases – you’re back in your usual role: architect, not just user.
For me, the principle is simple:
If I wouldn’t give a human service account read access to all of this without restrictions, I definitely shouldn’t do it for an AI agent either.
Copilot is powerful, but it doesn’t magically know what your company considers sensitive. That’s still your job. Security trimming is the line between “useful internal assistant” and “polite data leak on demand”.
2026-03-05 14:07:21

I've been building a personal AI agent — not a chatbot, a companion. One that knows my projects, preferences, and decisions. That picks up where we left off without me re-explaining everything.
But here's what nobody talks about: naive memory is expensive. And not just in dollars.
Give an agent a massive context window and fill it with everything it's ever seen. More context doesn't mean more understanding — it means more noise. The signal-to-noise ratio collapses. The agent hallucinates connections between unrelated things, loses track of what matters right now, and slows down while becoming less accurate.
Context isn't just a resource — it's a cognitive environment. Pollute it, and your agent gets dumber the more it "knows."
The human brain doesn't work this way. You don't replay every conversation you've ever had before answering a question. You forget most things. That forgetting isn't a bug — it's the architecture.
So I built memory that works more like ours:
Structured extraction over raw storage. Facts are extracted and stored independently. Decisions are recorded with confidence levels, reasoning, and outcomes. Conversations get summarized when they close — the insight survives, the verbatim dies.
Frame-aware budgets. Every interaction gets classified into a cognitive frame — conversation, task, decision, debug, research — each with a different token budget. A casual chat loads 3K tokens of context. A complex decision loads 12K with 3x more past decisions pulled in. The agent doesn't decide how much to remember — the frame does.
Batched retrieval. When the agent needs data from multiple sources, a single embedded script runs all the queries, filters and compresses results, and returns only what matters. Three tool calls that would each dump full results into context become one compact summary.
Aggressive pruning. Tool outputs get automatically trimmed as they age — results over 4K characters are soft-trimmed to the first and last 1,500 characters. After 6 tool calls, old outputs are cleared entirely. The agent never carries dead weight.
Intentional forgetting. Some things are forgotten on purpose.
The result? An agent that knows me across hundreds of conversations while using fewer tokens per turn than a basic chat with no memory at all. That is the idea :)
This is the real challenge in agentic AI. Not making agents that can do things — that's mostly solved. Making agents that can think economically. That carry context without carrying cost. That remember like a trusted colleague, not like a court stenographer.
We're entering an era where your AI's memory architecture matters more than its model. The smartest model with wasteful memory loses to a good model with intelligent recall.
Build agents that remember wisely. Not agents that remember everything.
https://github.com/tfatykhov/nous
P.S Still work in progress, but a lot has been done.
2026-03-05 13:58:21
2026-03-05 13:58:19
If you run Playwright tests locally, debugging failures is usually straightforward.
You run the test, open the trace viewer, inspect the DOM, and quickly figure out what went wrong.
But once Playwright runs inside CI, things start to get messy.
A typical failure workflow looks like this:
At this point debugging the test sometimes takes longer than fixing the issue.
The bigger the suite gets, the worse this becomes.
If tests run across 10 to 20 CI jobs (or shards), understanding what actually happened requires digging through traces, logs and screenshots across multiple artifacts.
In other words:
The slow part of Playwright debugging in CI is not root cause analysis.
It's reconstructing the failure context.
A few things make CI debugging easier:
• Enable trace: "on-first-retry" or trace: "retain-on-failure"
• Save screenshots/videos on failure
• Upload artifacts from CI
These are essential, but they still leave you with scattered artifacts that must be downloaded locally.
Instead of downloading artifacts, we started reconstructing the entire CI run into a single debugging view.
This way you can open a failed test and immediately inspect:
• trace
• screenshots
• logs
• video
• CI metadata
without downloading anything.
Here is a simple demo of what that looks like:
Curious how other teams debug Playwright failures in CI once their test suites start running across multiple jobs.