{"context":{"domain":"natura-x-app"},"layers":{"ApplicationLayer":{"reference":"ApplicationLayer","availableInstances":[{"reference":"ComponentsDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@DataSet","reference":"SystemsDataSet","params":{"filter":{"domain":"data2"},"pagination":{"size":1},"datasource":{"component":"data2-core-types@ProcessReference","params":{"reference":"SystemsDataSource"}}}},{"reference":"ExternalComponentInstancesDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"SystemsDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"5e3b796d6228227ca637d0f4","name":"data2","domain":"data2","organization":"62292184611f480b3191c37c","visibility":"public","updatedAt":"2026-07-27T14:19:36.810Z"}]}},{"reference":"OrganizationsDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"DataComponentInstancesDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@DataSet","reference":"OneFileComponentInstancesDataSet","params":{"pagination":{"size":1},"master":{"dataset":{"component":"data2-core-types@ProcessReference","params":{"reference":"SystemsDataSet"}},"localField":"system","masterField":"_id"},"datasource":{"component":"data2-core-types@ProcessReference","params":{"reference":"FileComponentInstancesDataSource"}}}},{"reference":"ApplicationsDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"ComponentInstancesDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"reference":"ServiceComponentInstancesDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@Data2TableDataSource","reference":"FileComponentInstancesDataSource","params":{"table":"fileComponentInstances","schema":[{"component":"data2-core-types@Field","params":{"key":"component","title":"Component","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"system","title":"System","component":"data2-core-primitives@ObjectId"}},{"component":"data2-core-types@Field","params":{"key":"displayName","title":"Display Name","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"params","title":"Params","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"reference","title":"Reference","component":"data2-core-primitives@String"}}]}},{"component":"data2-core@MemoryDataSource","reference":"EmptyDataSource","params":{"initialData":[]}},{"reference":"ComponentsDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"UserComponentsDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"DataComponentInstancesDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"ServiceComponentInstancesDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"ComponentInstancesDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"ExternalComponentInstancesDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}},{"reference":"ApplicationsDataSet","component":"data2-core@DataSet","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"EmptyDataSource"}}}}],"processesParams":{}},"HostApplicationLayer":{"reference":"HostApplicationLayer","availableInstances":[{"_id":"5f15ff190528b92601890f71","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"SystemsTableDataSource"}},"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"domain"}}},"pk":"_id","fields":[{"component":"data2-core-types@FieldReference","params":{"key":"_id"}},{"component":"data2-core-types@FieldReference","params":{"key":"name"}},{"component":"data2-core-types@FieldReference","params":{"key":"domain"}},{"component":"data2-core-types@FieldReference","params":{"key":"visibility"}},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"customDomains"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"organization._id"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"organization.name"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"organization.billingStatus"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"organization.publicTrial.active"}}},"break":true},{"component":"data2-core-types@FieldReference","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"settings.defaultAccessPolicy"}}},"break":true}],"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":1}}},"staticPagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":1}}},"policies":[{"component":"data2-core-auth@Policy","params":{"allow":{"params":{"actions":[{"params":{"components":[{"params":{"query":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$params.query"}}}},"field":{"component":"data2-core-primitives@String","params":{"content":"_id"}},"fieldType":"string"},"component":"data2@7KkQq0vK4a7D2gg6IVJT7"},{"params":{"query":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$params.query"}}}},"field":{"component":"data2-core-primitives@String","params":{"content":"domain"}},"fieldType":"string"},"component":"data2@7KkQq0vK4a7D2gg6IVJT7"},{"params":{"query":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$params.query"}}}},"field":{"component":"data2-core-primitives@String","params":{"content":"customDomains"}},"fieldType":"string"},"component":"data2@7KkQq0vK4a7D2gg6IVJT7"}]},"component":"data2-core-logical-operators@Or"}]},"component":"data2-core-flow@Series"}}}]},"reference":"SystemsDataSet","updatedAt":"2026-08-07T21:51:24.037Z","createdAt":"2020-07-20T20:31:21.838Z"},{"_id":"5f1601ce0528b92601890f7c","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":1000},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ComponentsTableDataSource"}},"sort":{"params.package":1,"params.component":1},"pk":"_id","autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"staticFilter":{"system":{"exists":{"params":{"content":false},"component":"data2-core-primitives@Boolean"}}},"filter":{"system":{"exists":{"params":{"content":false},"component":"data2-core-primitives@Boolean"}}}},"reference":"ComponentsDataSet","updatedAt":"2025-11-05T19:37:01.935Z","createdAt":"2020-07-20T20:42:54.898Z"},{"_id":"5f9041dbc8da12be8b94aea4","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":2000},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ComponentsTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"UserComponentsDataSet","updatedAt":"2025-11-06T21:06:58.095Z","createdAt":"2020-07-20T20:44:09.350Z"},{"_id":"65d8c4fabc9dbb8f94de8d53","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ExternalComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ExternalComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:23.742Z","createdAt":"2024-02-23T16:17:01.816Z"},{"reference":"SystemsTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"65b399e996f1fc092ed2079e","name":"app","domain":"natura-x-app","visibility":"public","settings":{"defaultAccessPolicy":"private"},"updatedAt":"2026-08-07T15:22:57.967Z","createdAt":"2024-01-26T11:39:21.258Z"}]}},{"reference":"ComponentsTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"5f52a278a575399a45d239b4","component":"data2-core-types@Component","params":{"package":"data2-core-primitives","component":"Router","interactions":{"component":"data2-core-types@FieldSet","params":{"fields":{"goBack":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-types@Function"}},"definition":{"component":"data2-core-functions@Function","params":{"body":{"preventEvaluation":true,"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@This","params":{}},"prop":"history.goBack","args":[]}}}},"icon":{"component":"data2-core-primitives@String","params":{"content":"arrow-left"}},"visibility":"public"}},"goTo":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"definition":{"component":"data2-core-functions@Function","params":{"body":{"preventEvaluation":true,"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@This","params":{}},"prop":"history.push","args":[{"component":"data2-core-types@Reference","params":{"reference":"url"}},{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"preserveQuery"}}}}]}},"parameters":[{"component":"data2-core-primitives@String","params":{"content":"url"}},{"component":"data2-core-primitives@String","params":{"content":"preserveQuery"}}]},"break":true},"icon":{"component":"data2-core-primitives@String","params":{"content":"share"}},"visibility":"public","arguments":[{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Path to route"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Path to redirect user"}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-types@ComponentParameter"},{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Boolean"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Preserve Query"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Preserve query string parameters  preserve query parameters on navigating."}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-types@ComponentParameter"}]}},"open":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-types@Function"}},"definition":{"component":"data2-core-functions@Function","params":{"body":{"preventEvaluation":true,"component":"data2-core-types@Getter","params":{"module":{"component":"data2-core-types@This","params":{}},"prop":"history.push","args":[{"pathname":{"component":"data2-core-types@Reference","params":{"reference":"value"}}}]}},"parameters":["value"]}},"icon":{"component":"data2-core-primitives@String","params":{"content":"share"}},"visibility":"public","arguments":[{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Link to open"}},"description":{"component":"data2-core-primitives@String","params":{"content":""}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-types@ComponentParameter"},{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Target"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Where to open the link"}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"oneOf":[{"params":{"content":"_blank"},"component":"data2-core-primitives@String"},{"params":{"content":"_self"},"component":"data2-core-primitives@String"},{"params":{"content":"_parent"},"component":"data2-core-primitives@String"},{"params":{"content":"_top"},"component":"data2-core-primitives@String"}]},"component":"data2-core-types@ComponentParameter"}]}},"close":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-types@Function"}},"definition":{"component":"data2-core-functions@Function","params":{"body":{"preventEvaluation":true,"component":"data2-core-types@Getter","params":{"module":{"params":{},"component":"data2-core-web@Window"},"prop":"close"}},"parameters":["value"]}},"icon":{"component":"data2-core-primitives@String","params":{"content":"times"}},"visibility":"public","arguments":[{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Link to open"}},"description":{"component":"data2-core-primitives@String","params":{"content":""}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2-core-types@ComponentParameter"},{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Target"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Where to open the link"}},"visibility":"public","required":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"oneOf":[{"params":{"content":"_blank"},"component":"data2-core-primitives@String"},{"params":{"content":"_self"},"component":"data2-core-primitives@String"},{"params":{"content":"_parent"},"component":"data2-core-primitives@String"},{"params":{"content":"_top"},"component":"data2-core-primitives@String"}]},"component":"data2-core-types@ComponentParameter"}],"description":{"component":"data2-core-primitives@String","params":{"content":"Closes a window that was opened via router.open."}}}}}}}}},{"_id":"620d28658271aa4129919d89","component":"data2-core-types@Component","params":{"package":"data2-interface-services","component":"DeviceController","interactions":{"params":{"fields":{"postMessage":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":"data2-core-primitives@String","title":{"component":"data2-core-primitives@String","params":{"content":"data"}}}}]},"component":"data2-core-types@ComponentParameter"},"print":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Object"}},"flat":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"parameters":{"zoom":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Number"}},"title":{"component":"data2-core-primitives@String","params":{"content":"zoom"}},"visibility":"public"}}},"visibility":"public"},"component":"data2-core-types@ComponentParameter"}],"visibility":"public"},"component":"data2-core-types@ComponentParameter"},"share":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}}},"component":"data2-core-types@ComponentParameter"},"clipboard.copy":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}}}}]},"component":"data2-core-types@ComponentParameter"},"clipboard.paste":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}}}}]},"component":"data2-core-types@ComponentParameter"},"download":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"public","arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Filename"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Content"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Object"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Options"}},"parameters":{"params":{"fields":{"type":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Mimetype"}}}}}},"component":"data2-core-types@FieldSet"}}}]},"component":"data2-core-types@ComponentParameter"},"downloadUrl":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"public","arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Filename"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Url"}}}}]},"component":"data2-core-types@ComponentParameter"},"promptFiles":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":"data2-core-primitives@Object","flat":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"parameters":{"params":{"fields":{"multiple":{"component":"data2-core-types@ComponentParameter","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Multiple"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Whether or not to allow selecting multiple files"}},"component":"data2-core-primitives@Boolean"}},"acceptedFormats":{"component":"data2-core-types@ComponentParameter","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Accepted Formats"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Accepted file formats"}},"component":"data2-core-primitives@String","list":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}},"timeout":{"component":"data2-core-types@ComponentParameter","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"Timeout"}},"description":{"component":"data2-core-primitives@String","params":{"content":"Max milliseconds to wait for file upload"}},"component":"data2-core-primitives@Number"}}}},"component":"data2-core-types@FieldSet"}}}],"visibility":"public"},"component":"data2-core-types@ComponentParameter"},"meetingRecord":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"public","definition":{"params":{"actions":[{"params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"meeting"}},"script":{"component":"data2-core-primitives@String","params":{"content":"(function() {\n  let mediaRecorder;\n  let audioChunks = [];\n  let combinedStream;\n  let audioContext;\n  \n  window.meetingCapture = async function() {\n    try {\n      // Solicitar acesso ao áudio da guia (e vídeo, que será ignorado depois)\n      const tabStream = await navigator.mediaDevices.getDisplayMedia({ \n        video: true, // Necessário para alguns navegadores\n        audio: true\n      });\n      \n      // Remover a trilha de vídeo\n      const tabAudioTrack = tabStream.getAudioTracks()[0];\n      \n      // Solicitar acesso ao microfone\n      const micStream = await navigator.mediaDevices.getUserMedia({ audio: true });\n      const micAudioTrack = micStream.getAudioTracks()[0];\n      \n      // Criar um novo MediaStream com ambas as trilhas de áudio\n      combinedStream = new MediaStream([tabAudioTrack, micAudioTrack]);\n      \n      // Criar um contexto de áudio para misturar os streams\n      audioContext = new AudioContext();\n      const destination = audioContext.createMediaStreamDestination();\n      \n      // Conectar a trilha de áudio da guia\n      const tabSource = audioContext.createMediaStreamSource(new MediaStream([tabAudioTrack]));\n      tabSource.connect(destination);\n      \n      // Conectar a trilha de áudio do microfone\n      const micSource = audioContext.createMediaStreamSource(new MediaStream([micAudioTrack]));\n      micSource.connect(destination);\n      \n      // Iniciar gravação com o stream misturado\n      mediaRecorder = new MediaRecorder(destination.stream);\n      audioChunks = [];\n      mediaRecorder.ondataavailable = (e) => audioChunks.push(e.data);\n      mediaRecorder.start();\n      \n      console.log('Gravação iniciada. Chame meetingCaptureStop() para finalizar.');\n    } catch (error) {\n      console.error('Erro ao iniciar a captura:', error);\n    }\n  };\n  \n  window.meetingCaptureStop = function() {\n    return new Promise((resolve, reject) => {\n      if (!mediaRecorder) {\n        reject('Nenhuma gravação em andamento.');\n        return;\n      }\n      \n      mediaRecorder.onstop = () => {\n        const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });\n        combinedStream.getTracks().forEach(track => track.stop());\n        audioContext.close();\n        \n        resolve(audioBlob);\n        console.log('Gravação finalizada. O arquivo de áudio está pronto para upload.');\n      };\n      \n      mediaRecorder.stop();\n    });\n  };\n})();"}}},"component":"data2-core-web@Script"},{"component":"data2-core-types@Getter","params":{"module":{"params":{},"component":"data2-core-web@Window"},"prop":{"component":"data2-core-primitives@String","params":{"content":"meetingCapture"}}}}]},"component":"data2-core-flow@Series"}},"component":"data2-core-types@ComponentParameter"},"meetingRecordStop":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"public","arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Filename"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Content"}}}},{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Object"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Options"}},"parameters":{"params":{"fields":{"type":{"component":"data2-core-types@ComponentParameter","params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@String"}},"title":{"component":"data2-core-primitives@String","params":{"content":"Mimetype"}}}}}},"component":"data2-core-types@FieldSet"}}}],"definition":{"params":{"actions":[{"component":"data2-core-types@Getter","params":{"module":{"params":{},"component":"data2-core-web@Window"},"prop":{"component":"data2-core-primitives@String","params":{"content":"meetingCaptureStop"}}}}]},"component":"data2-core-flow@Series"}},"component":"data2-core-types@ComponentParameter"},"notify":{"params":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core-primitives@Function"}},"visibility":"private","arguments":[{"component":"data2-core-types@ComponentParameter","params":{"component":"data2-core-primitives@Object","parameters":{"title":{"params":{"component":"data2-core-primitives@String","index":{"component":"data2-core-primitives@Number","params":{"content":0}},"title":{"component":"data2-core-primitives@String","params":{"content":"title"}}},"component":"data2-core-types@ComponentParameter"},"sound":{"params":{"component":"data2-core-types@FileComponentUrlReference","index":{"component":"data2-core-primitives@Number","params":{"content":1}},"title":{"component":"data2-core-primitives@String","params":{"content":"sound"}}},"component":"data2-core-types@ComponentParameter"}},"flat":{"component":"data2-core-primitives@Boolean","params":{"content":true}}}}]},"component":"data2-core-types@ComponentParameter"}}},"component":"data2-core-types@FieldSet"}}},{"_id":"6a75c0c6b56e0bf3babcc1c9","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kIAPBoard","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"const STATUS_META = {\n  a_fazer:      { label: 'A fazer',       color: '#8792A2', bg: 'rgba(135,146,162,0.16)' },\n  em_andamento: { label: 'Em andamento',  color: '#E8A93A', bg: 'rgba(232,169,58,0.20)' },\n  on_hold:      { label: 'Em pausa',      color: '#B98A5A', bg: 'rgba(185,138,90,0.20)' },\n  feito:        { label: 'Concluído',     color: '#4C9A6B', bg: 'rgba(76,154,107,0.20)' }\n}\nconst STATUS_ORDER = ['a_fazer', 'em_andamento', 'on_hold', 'feito']\n\nconst PRIORITY_META = {\n  baixa: { label: 'Baixa', color: '#7C8593' },\n  media: { label: 'Média', color: '#E8A93A' },\n  alta:  { label: 'Alta',  color: '#C4123F' }\n}\nconst PRIORITY_ORDER = ['baixa', 'media', 'alta']\n\nconst PUBLICO_META = {\n  GN:     { label: 'GN',     color: '#2E6B8C' },\n  GV:     { label: 'GV',     color: '#4C7A5A' },\n  LIDER:  { label: 'Líder',  color: '#C97A1A' },\n  NATURA: { label: 'Natura', color: '#7A5C99' }\n}\nconst PUBLICO_ORDER = ['GN', 'GV', 'LIDER', 'NATURA']\n\nconst PERSON_PALETTE = ['#C4123F', '#2E6B8C', '#C97A1A', '#4C7A5A', '#7A5C99', '#B23A6B', '#3D8C8C']\n\nconst LABEL_H = 60\n\nlet TEMAS = []\nlet CANVAS_W = 3000\nlet CANVAS_H = 1980\n\nfunction computeTemaLayout (temaRecords, tasks) {\n  const COLS = 3\n  const COL_W = 620\n  const GAP = 40\n  const MARGIN = 60\n  const CARDS_PER_ROW = 2\n  const colX = []\n  for (let i = 0; i < COLS; i++) colX.push(MARGIN + i * (COL_W + GAP))\n  const colY = colX.map(() => MARGIN)\n  const out = temaRecords.map((rec, idx) => {\n    const n = tasks.filter(x => x.tema === rec.nome).length\n    const rows = Math.max(1, Math.ceil(n / CARDS_PER_ROW))\n    const h = LABEL_H + 20 + rows * 160\n    const col = idx % COLS\n    const item = { tema: rec.nome, _id: rec._id, ordem: (rec.ordem || 0), x: colX[col], y: colY[col], w: COL_W, h: h }\n    colY[col] += h + GAP\n    return item\n  })\n  return out\n}\n\nfunction computeCanvasSize (temasList) {\n  let maxX = 1600\n  let maxY = 1200\n  temasList.forEach(t => {\n    maxX = Math.max(maxX, t.x + t.w + 200)\n    maxY = Math.max(maxY, t.y + t.h + 200)\n  })\n  return { w: maxX, h: maxY }\n}\nconst VIEWPORT_W = 1600\nconst VIEWPORT_H = 888\n\nconst GROUPBY_OPTIONS = [\n  { key: 'tema', label: 'Tema' },\n  { key: 'status', label: 'Status' },\n  { key: 'priority', label: 'Prioridade' },\n  { key: 'assignee', label: 'Responsável' },\n  { key: 'publico', label: 'Público' }\n]\n\nfunction getLanes (groupBy, people) {\n  if (groupBy === 'tema') return TEMAS.map(t => ({ key: t.tema, label: t.tema, color: '#8792A2' }))\n  if (groupBy === 'status') return STATUS_ORDER.map(s => ({ key: s, label: STATUS_META[s].label, color: STATUS_META[s].color }))\n  if (groupBy === 'priority') return PRIORITY_ORDER.map(p => ({ key: p, label: PRIORITY_META[p].label, color: PRIORITY_META[p].color }))\n  if (groupBy === 'assignee') return people.map(p => ({ key: String(p._id), label: p.name, color: p.color })).concat([{ key: '', label: 'Sem responsável', color: '#6E757C' }])\n  if (groupBy === 'publico') return PUBLICO_ORDER.map(p => ({ key: p, label: PUBLICO_META[p].label, color: PUBLICO_META[p].color })).concat([{ key: '__none__', label: 'Sem público definido', color: '#6E757C' }])\n  return []\n}\n\nfunction taskInLane (groupBy, task, laneKey) {\n  if (groupBy === 'tema') return task.tema === laneKey\n  if (groupBy === 'status') return task.status === laneKey\n  if (groupBy === 'priority') return task.priority === laneKey\n  if (groupBy === 'assignee') return String(task.assignee_id || '') === String(laneKey)\n  if (groupBy === 'publico') return laneKey === '__none__' ? (task.publicos || []).length === 0 : (task.publicos || []).includes(laneKey)\n  return false\n}\n\nfunction applyDropValue (groupBy, task, sourceLane, targetLane) {\n  if (groupBy === 'tema') return { field: 'tema', value: targetLane }\n  if (groupBy === 'status') return { field: 'status', value: targetLane }\n  if (groupBy === 'priority') return { field: 'priority', value: targetLane }\n  if (groupBy === 'assignee') return { field: 'assignee_id', value: targetLane || null }\n  if (groupBy === 'publico') {\n    let next = (task.publicos || []).slice()\n    if (sourceLane !== '__none__') next = next.filter(x => x !== sourceLane)\n    if (targetLane !== '__none__' && !next.includes(targetLane)) next.push(targetLane)\n    return { field: 'publicos', value: next }\n  }\n  return null\n}\n\nfunction temaMeta (tema) {\n  return TEMAS.find(t => t.tema === tema) || TEMAS[0]\n}\nfunction esc (s) {\n  return String(s == null ? '' : s)\n    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\nfunction clamp (v, lo, hi) { return Math.max(lo, Math.min(hi, v)) }\nfunction initials (name) {\n  const n = String(name || '').trim()\n  if (!n) return '?'\n  const parts = n.split(/\\s+/)\n  return (parts[0][0] + (parts[1] ? parts[1][0] : '')).toUpperCase()\n}\nfunction fmtWhen (iso) {\n  try {\n    const d = new Date(iso)\n    if (isNaN(d.getTime())) return ''\n    return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' }) + ' ' +\n      d.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })\n  } catch (e) { return '' }\n}\nfunction nowIso () { return new Date().toISOString() }\nfunction personById (people, id) {\n  if (!id) return null\n  return people.find(p => String(p._id) === String(id)) || null\n}\nfunction nextPersonColor (people) {\n  return PERSON_PALETTE[people.length % PERSON_PALETTE.length]\n}\nfunction taskTitleById (tasks, id) {\n  if (!id) return 'Geral'\n  const t = tasks.find(x => String(x._id) === String(id))\n  return t ? t.title : 'Tarefa removida'\n}\nfunction roleLabel (role) {\n  if (role === 'tech-lead') return 'Tech Lead'\n  if (role === 'interno' || role === 'squad') return 'Squad Maia'\n  if (role === 'cliente') return 'Cliente Natura'\n  return role || 'Membro'\n}\n\nfunction buildActivity (task, action, field, oldValue, newValue, by) {\n  return {\n    entity: 'task',\n    entity_id: task && task._id ? String(task._id) : '',\n    entity_title: task && task.title ? task.title : '',\n    action: action || 'update',\n    field: field || '',\n    old_value: oldValue == null ? '' : String(oldValue),\n    new_value: newValue == null ? '' : String(newValue),\n    by: by || 'Cliente',\n    at: nowIso()\n  }\n}\n\nfunction countBy (tasks, key) {\n  const out = {}\n  tasks.forEach(t => { const k = t[key] || '—'; out[k] = (out[k] || 0) + 1 })\n  return out\n}\n\n\n\nfunction renderTemaAdminModal (d2, temasList, tasks) {\n  if (!d2.state.temaAdminOpen) return ''\n  const err = d2.state.temaAdminError\n  const rows = temasList.map(t => {\n    const n = tasks.filter(x => x.tema === t.tema).length\n    const editing = d2.state.temaEditingId === t._id\n    return `\n      <div class=\"tema-row\">\n        ${editing\n          ? `<input type=\"text\" class=\"tema-rename-input\" data-id=\"${t._id}\" value=\"${esc(t.tema)}\" />\n             <button class=\"btn btn-primary btn-sm\" data-act=\"save-tema-rename\" data-id=\"${t._id}\">Salvar</button>\n             <button class=\"btn btn-ghost btn-sm\" data-act=\"cancel-tema-rename\">Cancelar</button>`\n          : `<span class=\"tema-row-name\">${esc(t.tema)}</span>\n             <span class=\"tema-row-count\">${n} tarefa${n === 1 ? '' : 's'}</span>\n             <button class=\"btn btn-ghost btn-sm\" data-act=\"start-tema-rename\" data-id=\"${t._id}\">Renomear</button>\n             <button class=\"btn btn-ghost btn-sm\" data-act=\"delete-tema\" data-id=\"${t._id}\" data-count=\"${n}\">Apagar</button>`}\n      </div>`\n  }).join('')\n  return `\n    <div class=\"overlay-scrim\" data-act=\"close-tema-admin\">\n      <div class=\"modal-panel tema-admin-panel\" data-act=\"stop\">\n        <button class=\"close-x\" data-act=\"close-tema-admin\">×</button>\n        <div class=\"modal-title\">Gerenciar temas</div>\n        ${err ? `<div class=\"login-error\">${esc(err)}</div>` : ''}\n        <div class=\"tema-admin-list\">${rows || '<div class=\"empty-hint\">Nenhum tema ainda.</div>'}</div>\n        <div class=\"tema-admin-add\">\n          <input type=\"text\" class=\"new-tema-input\" placeholder=\"Nome do novo tema\" />\n          <button class=\"btn btn-primary btn-sm\" data-act=\"add-tema\">+ Adicionar tema</button>\n        </div>\n      </div>\n    </div>`\n}\n\nfunction renderChangePasswordModal (d2) {\n  if (!d2.state.changePasswordOpen) return ''\n  const err = d2.state.cpError\n  return `\n    <div class=\"overlay-scrim\" data-act=\"close-change-password\">\n      <div class=\"modal-panel\" data-act=\"stop\">\n        <button class=\"close-x\" data-act=\"close-change-password\">×</button>\n        <div class=\"modal-title\">Alterar senha</div>\n        ${err ? `<div class=\"login-error\">${esc(err)}</div>` : ''}\n        <input type=\"password\" class=\"cp-current\" placeholder=\"Senha atual\" autocomplete=\"current-password\" />\n        <input type=\"password\" class=\"cp-new\" placeholder=\"Nova senha\" autocomplete=\"new-password\" />\n        <input type=\"password\" class=\"cp-confirm\" placeholder=\"Confirmar nova senha\" autocomplete=\"new-password\" />\n        <div class=\"modal-actions\">\n          <button class=\"btn btn-ghost\" data-act=\"close-change-password\">Cancelar</button>\n          <button class=\"btn btn-primary\" data-act=\"submit-change-password\">Salvar nova senha</button>\n        </div>\n      </div>\n    </div>`\n}\n\nfunction renderHeader (d2, tasks, currentUser) {\n  const counts = countBy(tasks, 'status')\n  const view = d2.state.view || 'canvas'\n  const chips = STATUS_ORDER.map(s => {\n    const m = STATUS_META[s]\n    const n = counts[s] || 0\n    return `<span class=\"stat-chip\" style=\"--c:${m.color}\"><b>${n}</b>${esc(m.label)}</span>`\n  }).join('')\n  return `\n    <header class=\"topbar no-pan\">\n      <div class=\"brand\">\n        <span class=\"brand-mark\">IAP</span>\n        <div class=\"brand-text\">\n          <div class=\"brand-title\">Painel de Gestão — Experimento IAP</div>\n          <div class=\"brand-sub\">Só o rastreamento deste experimento — a sustentação da Maia continua no Jira</div>\n        </div>\n      </div>\n      <div class=\"stat-row\">${chips}</div>\n      <div class=\"topbar-actions\">\n        <span class=\"who-chip\">${esc(currentUser.name)}<span class=\"who-role\">${esc(roleLabel(currentUser.role))}</span></span>\n        <button class=\"btn btn-ghost btn-sm\" data-act=\"open-tema-admin\">Gerenciar temas</button>\n        <button class=\"btn btn-ghost btn-sm\" data-act=\"open-change-password\">Alterar senha</button>\n        <button class=\"btn btn-ghost btn-sm\" data-act=\"do-logout\">Sair</button>\n        <div class=\"view-switch\" data-act=\"view-switch\">\n          <button class=\"view-btn ${view === 'canvas' ? 'is-active' : ''}\" data-view=\"canvas\">Mapa vivo</button>\n          <button class=\"view-btn ${view === 'board' ? 'is-active' : ''}\" data-view=\"board\">Quadro</button>\n          <button class=\"view-btn ${view === 'list' ? 'is-active' : ''}\" data-view=\"list\">Lista</button>\n          <button class=\"view-btn ${view === 'central' ? 'is-active' : ''}\" data-view=\"central\">Central</button>\n        </div>\n        <button class=\"btn btn-primary\" data-act=\"new-task\">+ Nova tarefa</button>\n      </div>\n    </header>`\n}\n\nfunction renderTemaNav (d2, tasks) {\n  const pills = TEMAS.map(t => {\n    const n = tasks.filter(x => x.tema === t.tema).length\n    return `<button class=\"tema-pill no-pan\" data-act=\"jump\" data-tema=\"${esc(t.tema)}\">${esc(t.tema)} <b>${n}</b></button>`\n  }).join('')\n  return `<nav class=\"tema-nav no-pan\">${pills}</nav>`\n}\n\nfunction renderCard (t, people, comments, files) {\n  const sm = STATUS_META[t.status] || STATUS_META.a_fazer\n  const pm = PRIORITY_META[t.priority] || PRIORITY_META.media\n  const assignee = personById(people, t.assignee_id)\n  const publicos = (t.publicos || []).slice(0, 3).map(p => {\n    const m = PUBLICO_META[p]\n    return m ? `<span class=\"chip-mini\" style=\"--c:${m.color}\">${esc(m.label)}</span>` : ''\n  }).join('')\n  const extra = (t.publicos || []).length > 3 ? `<span class=\"chip-mini chip-more\">+${(t.publicos.length - 3)}</span>` : ''\n  const commentCount = comments.filter(c => String(c.task_id) === String(t._id)).length\n  const pendingFiles = files.filter(f => String(f.task_id) === String(t._id) && f.status === 'pendente').length\n  const anchor = temaMeta(t.tema)\n  const idx = typeof t._clusterIdx === 'number' ? t._clusterIdx : 0\n  const left = anchor.x + 20 + (idx % 2) * 300\n  const top = anchor.y + LABEL_H + 20 + Math.floor(idx / 2) * 160\n  return `\n    <div class=\"task-card no-pan ${t._savingField ? 'is-saving' : ''}\" data-key=\"${t._id}\" data-act=\"open-task\" data-id=\"${t._id}\"\n         style=\"left:${left}px; top:${top}px; --pcolor:${pm.color}\">\n      <div class=\"card-top\">\n        <span class=\"status-dot\" style=\"--c:${sm.color}\" title=\"${esc(sm.label)}\"></span>\n        <span class=\"card-status\" style=\"--c:${sm.color}\">${esc(sm.label)}</span>\n      </div>\n      <div class=\"card-title\">${esc(t.title)}</div>\n      <div class=\"card-tags\">${publicos}${extra}</div>\n      <div class=\"card-foot\">\n        ${assignee\n          ? `<span class=\"mini-avatar\" style=\"--c:${assignee.color}\" title=\"${esc(assignee.name)}\">${esc(initials(assignee.name))}</span>`\n          : `<span class=\"mini-avatar mini-avatar-empty\">—</span>`}\n        <span class=\"foot-spacer\"></span>\n        ${commentCount ? `<span class=\"foot-badge\" title=\"dúvidas\">💬 ${commentCount}</span>` : ''}\n        ${pendingFiles ? `<span class=\"foot-badge foot-badge-warn\" title=\"arquivo pendente\">📎 ${pendingFiles}</span>` : ''}\n      </div>\n    </div>`\n}\n\nfunction renderClusters (tasks, people, comments, files) {\n  return TEMAS.map(t => {\n    const items = tasks.filter(x => x.tema === t.tema)\n    items.forEach((it, i) => { it._clusterIdx = i })\n    return `\n      <div class=\"cluster-blob\" style=\"left:${t.x}px; top:${t.y}px; width:${t.w}px; height:${t.h}px;\"></div>\n      <div class=\"cluster-label no-pan\" style=\"left:${t.x + 20}px; top:${t.y + 14}px;\">\n        ${esc(t.tema)} <span class=\"cluster-count\">${items.length}</span>\n      </div>\n      ${items.map(x => renderCard(x, people, comments, files)).join('')}\n    `\n  }).join('')\n}\n\nfunction renderCanvas (d2, tasks, people, comments, files) {\n  const zoom = d2.state.zoom || 0.82\n  const panX = d2.state.panX || 0\n  const panY = d2.state.panY || 0\n  return `\n    <div class=\"viewport\" data-act=\"viewport\">\n      <div class=\"canvas-layer\" style=\"width:${CANVAS_W}px; height:${CANVAS_H}px; transform:translate(${panX}px,${panY}px) scale(${zoom});\">\n        ${renderClusters(tasks, people, comments, files)}\n      </div>\n      <div class=\"zoom-ctl no-pan\">\n        <button class=\"zoom-btn\" data-act=\"zoom-out\">−</button>\n        <span class=\"zoom-val\">${Math.round(zoom * 100)}%</span>\n        <button class=\"zoom-btn\" data-act=\"zoom-in\">+</button>\n        <button class=\"zoom-btn zoom-fit-btn\" data-act=\"zoom-fit\" title=\"Encaixar tudo na tela\">⤢</button>\n      </div>\n      <button class=\"fab no-pan\" data-act=\"new-task\" title=\"Nova tarefa\">+</button>\n    </div>`\n}\n\nfunction renderBoardCard (t, people, comments, files, groupBy, laneKey) {\n  const sm = STATUS_META[t.status] || STATUS_META.a_fazer\n  const pm = PRIORITY_META[t.priority] || PRIORITY_META.media\n  const assignee = personById(people, t.assignee_id)\n  const publicos = (t.publicos || []).map(p => PUBLICO_META[p] ? `<span class=\"chip-mini\" style=\"--c:${PUBLICO_META[p].color}\">${esc(PUBLICO_META[p].label)}</span>` : '').join('')\n  const commentCount = comments.filter(c => String(c.task_id) === String(t._id)).length\n  const pendingFiles = files.filter(f => String(f.task_id) === String(t._id) && f.status === 'pendente').length\n  return `\n    <div class=\"board-card no-pan ${t._savingField ? 'is-saving' : ''}\" data-key=\"${t._id}__${esc(laneKey)}\" data-act=\"open-task\" data-id=\"${t._id}\"\n         data-drag-task=\"${t._id}\" data-group-by=\"${groupBy}\" data-source-lane=\"${esc(laneKey)}\"\n         style=\"--pcolor:${pm.color}\">\n      <div class=\"card-top\">\n        <span class=\"status-dot\" style=\"--c:${sm.color}\"></span>\n        <span class=\"card-status\" style=\"--c:${sm.color}\">${esc(sm.label)}</span>\n      </div>\n      <div class=\"card-title\">${esc(t.title)}</div>\n      <div class=\"card-tags\">${publicos}</div>\n      <div class=\"card-foot\">\n        ${assignee\n          ? `<span class=\"mini-avatar\" style=\"--c:${assignee.color}\" title=\"${esc(assignee.name)}\">${esc(initials(assignee.name))}</span>`\n          : `<span class=\"mini-avatar mini-avatar-empty\">—</span>`}\n        <span class=\"foot-spacer\"></span>\n        ${commentCount ? `<span class=\"foot-badge\" title=\"dúvidas\">💬 ${commentCount}</span>` : ''}\n        ${pendingFiles ? `<span class=\"foot-badge foot-badge-warn\" title=\"arquivo pendente\">📎 ${pendingFiles}</span>` : ''}\n      </div>\n    </div>`\n}\n\nfunction renderBoard (d2, tasks, people, comments, files) {\n  const groupBy = d2.state.boardGroupBy || 'status'\n  const lanes = getLanes(groupBy, people)\n  const switcher = GROUPBY_OPTIONS.map(o => `<button class=\"groupby-btn no-pan ${groupBy === o.key ? 'is-active' : ''}\" data-act=\"set-groupby\" data-value=\"${o.key}\">${esc(o.label)}</button>`).join('')\n  const prOrder = { alta: 0, media: 1, baixa: 2 }\n  const laneHtml = lanes.map(lane => {\n    const items = tasks.filter(t => taskInLane(groupBy, t, lane.key))\n      .sort((a, b) => (prOrder[a.priority] ?? 1) - (prOrder[b.priority] ?? 1) || String(a.title).localeCompare(String(b.title)))\n    return `\n      <div class=\"board-lane no-pan\" data-lane-key=\"${esc(lane.key)}\">\n        <div class=\"board-lane-header\">\n          <span class=\"board-lane-dot\" style=\"--c:${lane.color}\"></span>\n          <span class=\"board-lane-title\">${esc(lane.label)}</span>\n          <span class=\"board-lane-count\">${items.length}</span>\n        </div>\n        <div class=\"board-lane-body\">\n          ${items.map(t => renderBoardCard(t, people, comments, files, groupBy, lane.key)).join('') || '<div class=\"empty-hint board-lane-empty\">Nenhuma tarefa aqui.</div>'}\n        </div>\n      </div>`\n  }).join('')\n  return `\n    <div class=\"board-groupby-row no-pan\">${switcher}</div>\n    <div class=\"board-view no-pan\">${laneHtml}</div>`\n}\n\nfunction renderList (d2, tasks, people) {\n  const f = d2.state.listFilters || {}\n  const temaOpts = TEMAS.map(t => `<option value=\"${esc(t.tema)}\" ${f.tema === t.tema ? 'selected' : ''}>${esc(t.tema)}</option>`).join('')\n  const statusOpts = STATUS_ORDER.map(s => `<option value=\"${s}\" ${f.status === s ? 'selected' : ''}>${esc(STATUS_META[s].label)}</option>`).join('')\n  const prioOpts = PRIORITY_ORDER.map(s => `<option value=\"${s}\" ${f.priority === s ? 'selected' : ''}>${esc(PRIORITY_META[s].label)}</option>`).join('')\n  const pubOpts = PUBLICO_ORDER.map(s => `<option value=\"${s}\" ${f.publico === s ? 'selected' : ''}>${esc(PUBLICO_META[s].label)}</option>`).join('')\n  const assigneeOpts = people.map(p => `<option value=\"${p._id}\" ${f.assignee === String(p._id) ? 'selected' : ''}>${esc(p.name)}</option>`).join('')\n\n  let rows = tasks.slice()\n  if (f.tema) rows = rows.filter(t => t.tema === f.tema)\n  if (f.status) rows = rows.filter(t => t.status === f.status)\n  if (f.priority) rows = rows.filter(t => t.priority === f.priority)\n  if (f.publico) rows = rows.filter(t => (t.publicos || []).includes(f.publico))\n  if (f.assignee) rows = rows.filter(t => String(t.assignee_id) === f.assignee)\n  if (f.q && f.q.trim()) {\n    const q = f.q.trim().toLowerCase()\n    rows = rows.filter(t => String(t.title || '').toLowerCase().includes(q) || String(t.description || '').toLowerCase().includes(q))\n  }\n\n  const trs = rows.map(t => {\n    const sm = STATUS_META[t.status] || STATUS_META.a_fazer\n    const pm = PRIORITY_META[t.priority] || PRIORITY_META.media\n    const assignee = personById(people, t.assignee_id)\n    const pubs = (t.publicos || []).map(p => PUBLICO_META[p] ? `<span class=\"chip-mini\" style=\"--c:${PUBLICO_META[p].color}\">${esc(PUBLICO_META[p].label)}</span>` : '').join('')\n    return `\n      <tr class=\"list-row no-pan\" data-key=\"${t._id}\" data-act=\"open-task\" data-id=\"${t._id}\">\n        <td class=\"td-title\">${esc(t.title)}</td>\n        <td>${esc(t.tema)}</td>\n        <td><span class=\"pill-static\" style=\"--c:${sm.color}\">${esc(sm.label)}</span></td>\n        <td><span class=\"pill-static\" style=\"--c:${pm.color}\">${esc(pm.label)}</span></td>\n        <td class=\"td-tags\">${pubs}</td>\n        <td>${assignee ? esc(assignee.name) : '—'}</td>\n      </tr>`\n  }).join('')\n\n  return `\n    <div class=\"list-view no-pan\">\n      <div class=\"filters-row\">\n        <input type=\"text\" class=\"filter-search\" data-act=\"filter-search\" placeholder=\"Buscar por título ou descrição...\" value=\"${esc(f.q || '')}\" />\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"tema\"><option value=\"\">Tema — todos</option>${temaOpts}</select>\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"status\"><option value=\"\">Status — todos</option>${statusOpts}</select>\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"priority\"><option value=\"\">Prioridade — todas</option>${prioOpts}</select>\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"publico\"><option value=\"\">Público — todos</option>${pubOpts}</select>\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"assignee\"><option value=\"\">Responsável — todos</option>${assigneeOpts}</select>\n        <button class=\"btn btn-ghost\" data-act=\"clear-filters\">Limpar filtros</button>\n        <span class=\"filter-count\">${rows.length} de ${tasks.length} tarefas</span>\n      </div>\n      <div class=\"table-wrap\">\n        <table class=\"list-table\">\n          <thead><tr><th>Título</th><th>Tema</th><th>Status</th><th>Prioridade</th><th>Públicos</th><th>Responsável</th></tr></thead>\n          <tbody>${trs || '<tr><td colspan=\"6\" class=\"td-empty\">Nenhuma tarefa encontrada com esses filtros.</td></tr>'}</tbody>\n        </table>\n      </div>\n    </div>`\n}\n\nfunction renderKnowledgeTab (d2, knowledge) {\n  const uploading = d2.state.kbUploading\n  const pending = d2.state.kbPending\n  const sorted = knowledge.slice().sort((a, b) => new Date(b.uploaded_at || 0) - new Date(a.uploaded_at || 0))\n  const list = sorted.map(k => `\n    <div class=\"hub-row kb-row\">\n      <div class=\"hub-row-main\">\n        <div class=\"hub-row-title\">${esc(k.title)}</div>\n        <div class=\"file-meta\">${esc(k.file_name || '')} · adicionado por ${esc(k.uploaded_by || '—')} · ${fmtWhen(k.uploaded_at)}</div>\n        ${k.description ? `<div class=\"kb-desc-text\">${esc(k.description)}</div>` : ''}\n      </div>\n      <a class=\"pill-static\" style=\"--c:#4C9A6B\" href=\"${esc(k.file_url || '#')}\" target=\"_blank\" rel=\"noopener\">Abrir arquivo</a>\n      <button class=\"btn btn-ghost btn-sm\" data-act=\"delete-knowledge\" data-id=\"${k._id}\" title=\"Apagar\">🗑</button>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhum documento na base de conhecimento ainda.</div>'\n\n  const dzContent = uploading\n    ? `<div class=\"kb-dz-text\">Enviando arquivo…</div>`\n    : pending\n      ? `\n        <div class=\"kb-describe\">\n          <div class=\"kb-dz-text\">Arquivo enviado: <b>${esc(pending.file_name)}</b></div>\n          <input type=\"text\" class=\"kb-title\" placeholder=\"Título (ex.: Matriz de Ação IAP v2)\" />\n          <textarea class=\"kb-desc-input\" placeholder=\"Sobre o que é esse arquivo?\"></textarea>\n          <div class=\"kb-describe-actions\">\n            <button class=\"btn btn-ghost btn-sm\" data-act=\"kb-cancel\">Cancelar</button>\n            <button class=\"btn btn-primary btn-sm\" data-act=\"kb-submit\">Adicionar à base</button>\n          </div>\n        </div>`\n      : `\n        <div class=\"kb-dz-text\">Arraste um arquivo aqui para adicionar à base de conhecimento</div>\n        <label class=\"btn btn-ghost btn-sm kb-pick-label\">ou escolher arquivo<input type=\"file\" class=\"kb-file-input\" hidden /></label>`\n\n  return `\n    <div class=\"kb-dropzone ${uploading ? 'is-busy' : ''}\" data-act=\"kb-dropzone\">${dzContent}</div>\n    <div class=\"hub-list kb-list\">${list}</div>`\n}\n\nfunction renderCentral (d2, tasks, comments, files, knowledge) {\n  const tab = d2.state.centralTab || 'files'\n  const pendingCount = files.filter(f => f.status === 'pendente').length\n  const sortedFiles = files.slice().sort((a, b) => {\n    if (a.status !== b.status) return a.status === 'pendente' ? -1 : 1\n    return new Date(b.requested_at || 0) - new Date(a.requested_at || 0)\n  })\n  const sortedComments = comments.slice().sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0))\n  const taskOpts = `<option value=\"\">Geral (não ligado a uma tarefa)</option>` + tasks.map(t => `<option value=\"${t._id}\">${esc(t.title)}</option>`).join('')\n\n  const filesHtml = sortedFiles.map(f => `\n    <div class=\"hub-row\">\n      <div class=\"hub-row-main\">\n        <div class=\"hub-row-title\">${esc(f.label)}</div>\n        <button class=\"hub-tag\" data-act=\"jump-to-task\" data-id=\"${f.task_id || ''}\">${esc(taskTitleById(tasks, f.task_id))}</button>\n        <div class=\"file-meta\">pedido por ${esc(f.requested_by || '—')} · ${fmtWhen(f.requested_at)}</div>\n      </div>\n      ${f.status === 'recebido'\n        ? `<a class=\"pill-static\" style=\"--c:#4C9A6B\" href=\"${esc(f.file_url || '#')}\" target=\"_blank\" rel=\"noopener\">${esc(f.file_name || 'Ver arquivo')}</a>`\n        : `<label class=\"upload-btn\" style=\"--c:#B98A5A\">Pendente — enviar arquivo<input type=\"file\" class=\"file-input\" data-act=\"upload-file\" data-file-id=\"${f._id}\" hidden /></label>`}\n      <button class=\"btn btn-ghost btn-sm\" data-act=\"delete-file-request\" data-id=\"${f._id}\" title=\"Apagar\">🗑</button>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhum arquivo pedido ainda.</div>'\n\n  const hubTop = sortedComments.filter(c => !c.reply_to)\n  const hubRepliesOf = (id) => sortedComments.filter(c => String(c.reply_to || '') === String(id))\n  const hubReplyForm = (parentId, taskId) => d2.state.replyOpenId === parentId\n    ? `<div class=\"reply-inline\"><input type=\"text\" class=\"reply-input\" placeholder=\"Responder…\" /><button class=\"btn btn-ghost btn-sm\" data-act=\"send-reply\" data-parent=\"${parentId}\" data-task=\"${taskId || ''}\">Enviar</button></div>`\n    : ''\n  const commentsHtml = hubTop.map(c => `\n    <div class=\"hub-row hub-row-comment\">\n      <span class=\"mini-avatar\" style=\"--c:#6E757C\">${esc(initials(c.author_name))}</span>\n      <div class=\"comment-body\">\n        <div class=\"comment-meta\"><b>${esc(c.author_name || 'Anônimo')}</b>\n          <button class=\"hub-tag\" data-act=\"jump-to-task\" data-id=\"${c.task_id || ''}\">${esc(taskTitleById(tasks, c.task_id))}</button>\n          <span>${fmtWhen(c.created_at)}</span>\n          <button class=\"btn btn-ghost btn-sm\" data-act=\"toggle-reply\" data-id=\"${c._id}\">Responder</button>\n        </div>\n        <div class=\"comment-text\">${esc(c.body)}</div>\n        ${hubReplyForm(c._id, c.task_id)}\n        ${hubRepliesOf(c._id).map(r => `\n        <div class=\"hub-row hub-row-comment comment-reply\">\n          <span class=\"mini-avatar\" style=\"--c:#6E757C\">${esc(initials(r.author_name))}</span>\n          <div class=\"comment-body\">\n            <div class=\"comment-meta\"><b>${esc(r.author_name || 'Anônimo')}</b><span>${fmtWhen(r.created_at)}</span></div>\n            <div class=\"comment-text\">${esc(r.body)}</div>\n          </div>\n        </div>`).join('')}\n      </div>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhuma dúvida registrada ainda.</div>'\n\n  return `\n    <div class=\"central-view no-pan\">\n      <div class=\"central-intro\">\n        <div class=\"central-title\">Documentos &amp; Dúvidas</div>\n        <div class=\"central-sub\">Tudo que precisa de resposta ou upload, em um só lugar — vai se acumulando como a base de conhecimento do projeto.</div>\n      </div>\n      <div class=\"central-stats\">\n        <div class=\"stat-tile\">\n          <div class=\"stat-value\">${pendingCount}</div>\n          <div class=\"stat-label\"><span class=\"stat-dot\" style=\"--c:#B98A5A\"></span>Arquivo${pendingCount === 1 ? '' : 's'} pendente${pendingCount === 1 ? '' : 's'}</div>\n        </div>\n        <div class=\"stat-tile\">\n          <div class=\"stat-value\">${hubTop.filter(c => hubRepliesOf(c._id).length === 0).length}</div>\n          <div class=\"stat-label\"><span class=\"stat-dot\" style=\"--c:#C4123F\"></span>Dúvida${hubTop.filter(c => hubRepliesOf(c._id).length === 0).length === 1 ? '' : 's'} sem resposta</div>\n        </div>\n        <div class=\"stat-tile\">\n          <div class=\"stat-value\">${knowledge.length}</div>\n          <div class=\"stat-label\"><span class=\"stat-dot\" style=\"--c:#4C9A6B\"></span>Documento${knowledge.length === 1 ? '' : 's'} na base</div>\n        </div>\n        <div class=\"stat-tile\">\n          <div class=\"stat-value\">${hubTop.length}</div>\n          <div class=\"stat-label\"><span class=\"stat-dot\" style=\"--c:#8792A2\"></span>Dúvida${hubTop.length === 1 ? '' : 's'} no total</div>\n        </div>\n      </div>\n      <div class=\"central-tabs\">\n        <button class=\"central-tab ${tab === 'files' ? 'is-active' : ''}\" data-act=\"central-tab\" data-value=\"files\">Arquivos ${pendingCount ? `<b>${pendingCount} pendente${pendingCount > 1 ? 's' : ''}</b>` : ''}</button>\n        <button class=\"central-tab ${tab === 'comments' ? 'is-active' : ''}\" data-act=\"central-tab\" data-value=\"comments\">Dúvidas</button>\n        <button class=\"central-tab ${tab === 'knowledge' ? 'is-active' : ''}\" data-act=\"central-tab\" data-value=\"knowledge\">Base de Conhecimento</button>\n      </div>\n      ${tab === 'files' ? `\n        <div class=\"inline-form central-compose\">\n          <select class=\"hub-task-sel-file\">${taskOpts}</select>\n          <input type=\"text\" class=\"hub-fr-label\" placeholder=\"O que você precisa pedir?\" />\n          <button class=\"btn btn-ghost btn-sm\" data-act=\"hub-request-file\">Pedir arquivo</button>\n        </div>\n        <div class=\"hub-list\">${filesHtml}</div>\n      ` : tab === 'comments' ? `\n        <div class=\"inline-form central-compose\">\n          <select class=\"hub-task-sel-comment\">${taskOpts}</select>\n          <input type=\"text\" class=\"hub-comment-input\" placeholder=\"Escrever uma dúvida...\" />\n          <button class=\"btn btn-primary btn-sm\" data-act=\"hub-send-comment\">Enviar</button>\n        </div>\n        <div class=\"hub-list\">${commentsHtml}</div>\n      ` : renderKnowledgeTab(d2, knowledge)}\n    </div>`\n}\n\nfunction renderDetail (d2, tasks, people, comments, files) {\n  const id = d2.state.selectedTaskId\n  if (!id) return ''\n  const t = tasks.find(x => String(x._id) === String(id))\n  if (!t) return ''\n  const sm = STATUS_META[t.status] || STATUS_META.a_fazer\n  const taskComments = comments.filter(c => String(c.task_id) === String(t._id))\n    .sort((a, b) => new Date(a.created_at || 0) - new Date(b.created_at || 0))\n  const taskFiles = files.filter(f => String(f.task_id) === String(t._id))\n\n  const statusBtns = STATUS_ORDER.map(s => {\n    const m = STATUS_META[s]\n    return `<button class=\"pill-btn ${t.status === s ? 'is-active' : ''}\" style=\"--c:${m.color}\" data-act=\"set-status\" data-value=\"${s}\">${esc(m.label)}</button>`\n  }).join('')\n  const prioBtns = PRIORITY_ORDER.map(s => {\n    const m = PRIORITY_META[s]\n    return `<button class=\"pill-btn ${t.priority === s ? 'is-active' : ''}\" style=\"--c:${m.color}\" data-act=\"set-priority\" data-value=\"${s}\">${esc(m.label)}</button>`\n  }).join('')\n  const pubChips = PUBLICO_ORDER.map(p => {\n    const m = PUBLICO_META[p]\n    const active = (t.publicos || []).includes(p)\n    return `<button class=\"chip-btn ${active ? 'is-active' : ''}\" style=\"--c:${m.color}\" data-act=\"toggle-publico\" data-value=\"${p}\">${esc(m.label)}</button>`\n  }).join('')\n  const peopleOpts = `<option value=\"\">— Sem responsável —</option>` + people.map(p =>\n    `<option value=\"${p._id}\" ${String(t.assignee_id) === String(p._id) ? 'selected' : ''}>${esc(p.name)}${p.is_client ? ' (cliente)' : ''}</option>`\n  ).join('')\n\n  const topComments = taskComments.filter(c => !c.reply_to)\n  const repliesOf = (id) => taskComments.filter(c => String(c.reply_to || '') === String(id))\n  const renderReplyForm = (parentId) => d2.state.replyOpenId === parentId\n    ? `<div class=\"reply-inline\"><input type=\"text\" class=\"reply-input\" placeholder=\"Responder…\" /><button class=\"btn btn-ghost btn-sm\" data-act=\"send-reply\" data-parent=\"${parentId}\" data-task=\"${d2.state.selectedTaskId || ''}\">Enviar</button></div>`\n    : ''\n  const commentsHtml = topComments.map(c => `\n    <div class=\"comment-row\">\n      <span class=\"mini-avatar\" style=\"--c:#6E757C\">${esc(initials(c.author_name))}</span>\n      <div class=\"comment-body\">\n        <div class=\"comment-meta\"><b>${esc(c.author_name || 'Anônimo')}</b><span>${fmtWhen(c.created_at)}</span>\n          <button class=\"btn btn-ghost btn-sm\" data-act=\"toggle-reply\" data-id=\"${c._id}\">Responder</button>\n        </div>\n        <div class=\"comment-text\">${esc(c.body)}</div>\n        ${renderReplyForm(c._id)}\n        ${repliesOf(c._id).map(r => `\n        <div class=\"comment-row comment-reply\">\n          <span class=\"mini-avatar\" style=\"--c:#6E757C\">${esc(initials(r.author_name))}</span>\n          <div class=\"comment-body\">\n            <div class=\"comment-meta\"><b>${esc(r.author_name || 'Anônimo')}</b><span>${fmtWhen(r.created_at)}</span></div>\n            <div class=\"comment-text\">${esc(r.body)}</div>\n          </div>\n        </div>`).join('')}\n      </div>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhuma dúvida registrada nesta tarefa ainda.</div>'\n\n  const filesHtml = taskFiles.map(f => `\n    <div class=\"file-row\">\n      <div class=\"file-info\">\n        <div class=\"file-label\">${esc(f.label)}</div>\n        <div class=\"file-meta\">pedido por ${esc(f.requested_by || '—')} · ${fmtWhen(f.requested_at)}</div>\n      </div>\n      ${f.status === 'recebido'\n        ? `<a class=\"pill-static\" style=\"--c:#4C9A6B\" href=\"${esc(f.file_url || '#')}\" target=\"_blank\" rel=\"noopener\">Recebido — ver arquivo</a>`\n        : `<label class=\"upload-btn\" style=\"--c:#B98A5A\">Pendente — enviar arquivo<input type=\"file\" class=\"file-input\" data-act=\"upload-file\" data-file-id=\"${f._id}\" hidden /></label>`}\n      <button class=\"btn btn-ghost btn-sm\" data-act=\"delete-file-request\" data-id=\"${f._id}\" title=\"Apagar\">🗑</button>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhum arquivo pedido para esta tarefa.</div>'\n\n  return `\n    <div class=\"overlay-scrim\" data-act=\"close-overlay\">\n      <div class=\"detail-panel\" data-act=\"stop\">\n        <button class=\"close-x\" data-act=\"close-overlay\">×</button>\n        <div class=\"detail-head\">\n          <span class=\"status-dot\" style=\"--c:${sm.color}\"></span>\n          <input class=\"title-input\" type=\"text\" value=\"${esc(t.title)}\" data-act=\"edit-title\" data-id=\"${t._id}\" />\n        </div>\n        <textarea class=\"desc-input\" placeholder=\"Descrição — o que precisa ser feito, contexto, links...\" data-act=\"edit-desc\" data-id=\"${t._id}\">${esc(t.description || '')}</textarea>\n\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Status</div>\n          <div class=\"pill-row\">${statusBtns}</div>\n        </div>\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Prioridade</div>\n          <div class=\"pill-row\">${prioBtns}</div>\n        </div>\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Públicos</div>\n          <div class=\"pill-row\">${pubChips}</div>\n        </div>\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Responsável</div>\n          <div class=\"assignee-row\">\n            <select class=\"assignee-sel\" data-act=\"set-assignee\">${peopleOpts}</select>\n            <button class=\"btn btn-ghost btn-sm\" data-act=\"open-add-person\">+ Pessoa</button>\n          </div>\n          ${d2.state.addPersonOpen ? `\n          <div class=\"add-person-inline\">\n            <input type=\"text\" class=\"ap-name\" placeholder=\"Nome da pessoa\" />\n            <label class=\"ap-client\"><input type=\"checkbox\" checked data-act=\"ap-client\" /> é do cliente</label>\n            <button class=\"btn btn-primary btn-sm\" data-act=\"save-person\">Salvar pessoa</button>\n          </div>` : ''}\n        </div>\n\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Arquivos pedidos ao cliente</div>\n          <div class=\"file-list\">${filesHtml}</div>\n          <div class=\"inline-form\">\n            <input type=\"text\" class=\"fr-label\" placeholder=\"O que você precisa pedir? (ex.: planilha X)\" />\n            <button class=\"btn btn-ghost btn-sm\" data-act=\"request-file\" data-id=\"${t._id}\">Pedir arquivo</button>\n          </div>\n        </div>\n\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Dúvidas desta tarefa</div>\n          <div class=\"comment-list\">${commentsHtml}</div>\n          <div class=\"inline-form\">\n            <input type=\"text\" class=\"comment-input\" placeholder=\"Escrever uma dúvida ou observação...\" />\n            <button class=\"btn btn-primary btn-sm\" data-act=\"send-comment\" data-id=\"${t._id}\">Enviar</button>\n          </div>\n        </div>\n\n        <button class=\"btn btn-danger btn-sm delete-btn\" data-act=\"delete-task\" data-id=\"${t._id}\">Excluir tarefa</button>\n      </div>\n    </div>`\n}\n\nfunction renderNewTaskModal (d2) {\n  if (!d2.state.newTaskOpen) return ''\n  const temaOpts = TEMAS.map(t => `<option value=\"${esc(t.tema)}\">${esc(t.tema)}</option>`).join('')\n  const prioOpts = PRIORITY_ORDER.map(s => `<option value=\"${s}\" ${s === 'media' ? 'selected' : ''}>${esc(PRIORITY_META[s].label)}</option>`).join('')\n  const pubChecks = PUBLICO_ORDER.map(p => `\n    <label class=\"check-chip\"><input type=\"checkbox\" class=\"nt-publico\" value=\"${p}\" /> ${esc(PUBLICO_META[p].label)}</label>`).join('')\n  return `\n    <div class=\"overlay-scrim\" data-act=\"close-new-task\">\n      <div class=\"modal-panel\" data-act=\"stop\">\n        <button class=\"close-x\" data-act=\"close-new-task\">×</button>\n        <div class=\"modal-title\">Nova tarefa</div>\n        <input type=\"text\" class=\"nt-title\" placeholder=\"Título da tarefa\" />\n        <textarea class=\"nt-desc\" placeholder=\"Descrição (opcional)\"></textarea>\n        <div class=\"modal-row\">\n          <select class=\"nt-tema\">${temaOpts}</select>\n          <select class=\"nt-priority\">${prioOpts}</select>\n        </div>\n        <div class=\"modal-row nt-pub-row\">${pubChecks}</div>\n        <div class=\"modal-actions\">\n          <button class=\"btn btn-ghost\" data-act=\"close-new-task\">Cancelar</button>\n          <button class=\"btn btn-primary\" data-act=\"submit-new-task\">Criar tarefa</button>\n        </div>\n      </div>\n    </div>`\n}\n\nexport default {\n  css: `\n    :host { display:block; width:100%; height:100%; }\n    * { box-sizing:border-box; }\n    .app {\n      --bg:#14181C; --surface:#1E242B; --surface-2:#242B33; --border:rgba(255,255,255,0.08);\n      --text:#F2EDE7; --text-dim:#A9AFB6; --text-faint:#6E757C; --accent:#C4123F;\n      display:flex; flex-direction:column; width:100%; height:100%; background:var(--bg); color:var(--text);\n      font-family:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif; overflow:hidden; position:relative;\n    }\n    .loading-screen { display:flex; align-items:center; justify-content:center; width:100%; height:100%; background:var(--bg); color:var(--text-dim); font-size:14px; letter-spacing:0.03em; }\n\n    .login-screen { display:flex; align-items:center; justify-content:center; width:100%; height:100%; }\n    .login-card { width:360px; background:var(--surface-2); border:1px solid var(--border); border-radius:18px; padding:32px; display:flex; flex-direction:column; gap:12px; }\n    .login-brand { display:flex; justify-content:center; margin-bottom:4px; }\n    .login-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:19px; font-weight:600; text-align:center; }\n    .login-sub { font-size:12px; color:var(--text-faint); text-align:center; margin-bottom:8px; line-height:1.5; }\n    .login-error { font-size:12px; color:#E8798F; text-align:center; }\n    .login-notice { font-size:12px; color:#4C9A6B; text-align:center; }\n    .auth-tabs { display:flex; background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:999px; padding:3px; margin-bottom:4px; }\n    .auth-tab { flex:1; background:none; border:none; color:var(--text-dim); font-size:12px; padding:7px 10px; border-radius:999px; cursor:pointer; }\n    .auth-tab.is-active { background:var(--accent); color:#fff; }\n    .login-email, .login-password, .auth-email, .auth-password, .auth-password-confirm, .auth-name,\n    .cp-current, .cp-new, .cp-confirm { background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:9px; color:var(--text); font-size:13px; padding:10px 12px; width:100%; }\n    .cp-current, .cp-new, .cp-confirm { margin-bottom:10px; }\n    .login-submit { width:100%; margin-top:6px; }\n    .id-picker { width:400px; }\n    .id-card-list { display:flex; flex-direction:column; gap:8px; margin:6px 0 4px; }\n    .id-card { display:flex; align-items:center; gap:12px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:12px; padding:10px 14px; cursor:pointer; text-align:left; }\n    .id-card:hover { background:rgba(255,255,255,0.07); border-color:rgba(255,255,255,0.18); }\n    .id-card-name { flex:1; font-size:13.5px; font-weight:600; color:var(--text); }\n    .id-card-role { font-size:10.5px; color:var(--text-faint); text-transform:uppercase; letter-spacing:0.03em; }\n\n    .topbar { display:flex; align-items:center; gap:16px; height:68px; padding:0 24px; background:var(--surface-2); border-bottom:1px solid var(--border); flex-shrink:0; }\n    .brand { display:flex; align-items:center; gap:12px; }\n    .brand-mark { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-weight:700; font-size:15px; letter-spacing:0.08em; color:var(--accent); border:1px solid rgba(196,18,63,0.5); border-radius:8px; padding:4px 8px; }\n    .brand-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:17px; font-weight:600; line-height:1.15; }\n    .brand-sub { font-size:11px; color:var(--text-faint); margin-top:2px; }\n    .stat-row { display:flex; gap:10px; flex:1; justify-content:center; flex-wrap:wrap; }\n    .stat-chip { display:flex; align-items:center; gap:6px; font-size:12px; color:var(--text-dim); padding:5px 10px; border-radius:999px; background:rgba(255,255,255,0.03); border:1px solid var(--border); }\n    .stat-chip b { color:var(--c); font-size:13px; }\n    .topbar-actions { display:flex; align-items:center; gap:10px; }\n    .who-chip { font-size:12px; color:var(--text-dim); background:rgba(255,255,255,0.04); border:1px solid var(--border); padding:7px 12px; border-radius:999px; white-space:nowrap; }\n    .who-role { color:var(--text-faint); text-transform:uppercase; font-size:9.5px; margin-left:7px; letter-spacing:0.03em; }\n    .view-switch { display:flex; background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:999px; padding:3px; }\n    .view-btn { background:none; border:none; color:var(--text-dim); font-size:12px; padding:6px 14px; border-radius:999px; cursor:pointer; }\n    .view-btn.is-active { background:var(--accent); color:#fff; }\n    .btn { border-radius:9px; border:1px solid var(--border); background:rgba(255,255,255,0.04); color:var(--text); font-size:13px; padding:9px 16px; cursor:pointer; white-space:nowrap; transition:opacity .15s ease, transform .15s ease; }\n    .btn:hover { opacity:0.85; }\n    .btn:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }\n    .btn:focus:not(:focus-visible) { outline:none; }\n    .btn-primary { background:var(--accent); border-color:var(--accent); color:#fff; font-weight:600; }\n    .btn-ghost { background:transparent; }\n    .btn-danger { background:transparent; border-color:rgba(196,18,63,0.4); color:#E8798F; }\n    .btn-sm { padding:7px 12px; font-size:12px; }\n\n    .tema-nav { display:flex; gap:8px; padding:10px 24px; background:var(--surface-2); border-bottom:1px solid var(--border); overflow-x:auto; flex-shrink:0; }\n    .tema-pill { flex-shrink:0; background:rgba(255,255,255,0.04); border:1px solid var(--border); color:var(--text-dim); font-size:12px; padding:7px 12px; border-radius:999px; cursor:pointer; }\n    .tema-pill:hover { background:rgba(255,255,255,0.08); color:var(--text); }\n    .tema-pill b { color:var(--accent); margin-left:4px; }\n\n    .viewport { position:relative; flex:1; overflow:hidden; cursor:grab; background:\n        radial-gradient(circle at 1px 1px, rgba(255,255,255,0.05) 1px, transparent 0) 0 0/28px 28px, var(--bg); }\n    .viewport.is-grabbing { cursor:grabbing; }\n    .canvas-layer { position:absolute; left:0; top:0; transform-origin:0 0; }\n    .cluster-blob { position:absolute; border-radius:48px; background:radial-gradient(ellipse at 45% 35%, rgba(255,255,255,0.05), transparent 70%); pointer-events:none; }\n    .cluster-label { position:absolute; font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:13px; letter-spacing:0.03em; color:var(--text-dim); text-transform:uppercase; }\n    .cluster-count { color:var(--text-faint); font-size:11px; }\n\n    .is-saving { opacity:0.55; pointer-events:none; transition:opacity .15s ease; }\n    .task-card { position:absolute; width:216px; min-height:134px; background:var(--surface); border:1px solid var(--border); border-left:3px solid var(--pcolor); border-radius:14px; padding:12px 14px; cursor:pointer; box-shadow:0 1px 3px rgba(0,0,0,0.3); transition:transform .15s ease, box-shadow .15s ease; }\n    .task-card:hover { transform:translateY(-2px); box-shadow:0 8px 20px rgba(0,0,0,0.35); border-color:rgba(255,255,255,0.18); }\n    .card-top { display:flex; align-items:center; gap:6px; margin-bottom:8px; }\n    .status-dot { width:8px; height:8px; border-radius:50%; background:var(--c); flex-shrink:0; }\n    .card-status { font-size:10.5px; color:var(--c); text-transform:uppercase; letter-spacing:0.03em; font-weight:600; }\n    .card-title { font-size:13.5px; line-height:1.4; font-weight:600; color:var(--text); display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; }\n    .card-tags { display:flex; gap:4px; flex-wrap:wrap; margin-top:8px; }\n    .chip-mini { font-size:10px; padding:2px 7px; border-radius:999px; background:color-mix(in srgb, var(--c) 22%, transparent); color:var(--c); border:1px solid color-mix(in srgb, var(--c) 40%, transparent); }\n    .chip-more { color:var(--text-faint); background:rgba(255,255,255,0.06); border-color:var(--border); }\n    .card-foot { display:flex; align-items:center; gap:6px; margin-top:10px; }\n    .foot-spacer { flex:1; }\n    .foot-badge { font-size:10px; color:var(--text-faint); }\n    .foot-badge-warn { color:#E8A93A; }\n    .mini-avatar { width:22px; height:22px; border-radius:50%; background:var(--c); color:#fff; font-size:9.5px; font-weight:700; display:flex; align-items:center; justify-content:center; flex-shrink:0; }\n    .mini-avatar-empty { background:rgba(255,255,255,0.06); color:var(--text-faint); }\n\n    .zoom-ctl { position:absolute; right:20px; bottom:20px; display:flex; align-items:center; gap:8px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; padding:6px 10px; }\n    .zoom-btn { width:26px; height:26px; border-radius:50%; border:1px solid var(--border); background:rgba(255,255,255,0.04); color:var(--text); font-size:16px; cursor:pointer; line-height:1; }\n    .zoom-val { font-size:11px; color:var(--text-dim); width:38px; text-align:center; }\n    .fab { position:absolute; right:20px; top:20px; width:48px; height:48px; border-radius:50%; background:var(--accent); color:#fff; font-size:24px; border:none; cursor:pointer; box-shadow:0 6px 16px rgba(196,18,63,0.4); }\n\n    .list-view { flex:1; overflow:auto; padding:20px 24px; }\n    .filters-row { display:flex; gap:10px; align-items:center; margin-bottom:16px; flex-wrap:wrap; }\n    .filter-sel { background:var(--surface); border:1px solid var(--border); color:var(--text); font-size:12px; padding:8px 10px; border-radius:8px; }\n    .filter-search { background:var(--surface); border:1px solid var(--border); color:var(--text); font-size:12px; padding:8px 12px; border-radius:8px; min-width:220px; flex:1 1 220px; }\n    .filter-count { margin-left:auto; font-size:12px; color:var(--text-faint); }\n    .table-wrap { overflow-x:auto; }\n    .list-table { width:100%; border-collapse:collapse; font-size:13px; }\n    .list-table th { text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:0.04em; color:var(--text-faint); padding:8px 12px; border-bottom:1px solid var(--border); }\n    .list-row td { padding:12px; border-bottom:1px solid var(--border); cursor:pointer; }\n    .list-row:hover td { background:rgba(255,255,255,0.03); }\n    .td-title { font-weight:600; max-width:320px; }\n    .td-tags { display:flex; gap:4px; flex-wrap:wrap; }\n    .td-empty { text-align:center; color:var(--text-faint); padding:40px; }\n    .pill-static { font-size:11px; padding:4px 10px; border-radius:999px; color:var(--c); background:color-mix(in srgb, var(--c) 20%, transparent); border:1px solid color-mix(in srgb, var(--c) 40%, transparent); white-space:nowrap; }\n\n    .central-view { flex:1; overflow:auto; padding:28px 24px 40px; max-width:880px; margin:0 auto; width:100%; }\n    .central-intro { margin-bottom:20px; }\n    .central-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:21px; font-weight:600; }\n    .central-sub { font-size:12.5px; color:var(--text-faint); margin-top:6px; line-height:1.5; }\n    .central-stats { display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; margin-bottom:22px; }\n    .stat-tile { background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:14px 16px; }\n    .stat-value { font-family:system-ui,-apple-system,sans-serif; font-size:25px; font-weight:600; color:var(--text); line-height:1.1; }\n    .stat-label { display:flex; align-items:center; gap:6px; font-size:11px; color:var(--text-dim); margin-top:7px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n    .stat-dot { width:7px; height:7px; border-radius:50%; background:var(--c); flex-shrink:0; }\n    .central-tabs { display:flex; gap:8px; margin-bottom:18px; }\n    .central-tab { background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text-dim); font-size:13px; padding:9px 16px; border-radius:999px; cursor:pointer; }\n    .central-tab.is-active { background:var(--accent); border-color:var(--accent); color:#fff; }\n    .central-tab b { margin-left:6px; }\n    .central-compose { margin-bottom:20px; }\n    .central-compose select { background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:12px; padding:9px 10px; border-radius:8px; max-width:240px; }\n    .hub-list { display:flex; flex-direction:column; gap:10px; }\n    .hub-row { display:flex; justify-content:space-between; align-items:flex-start; gap:14px; background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:14px 16px; }\n    .hub-row-main { display:flex; flex-direction:column; gap:5px; }\n    .hub-row-title { font-size:13.5px; font-weight:600; }\n    .hub-tag { align-self:flex-start; font-size:10.5px; color:var(--text-dim); background:rgba(255,255,255,0.06); border:1px solid var(--border); padding:2px 9px; border-radius:999px; cursor:pointer; }\n\n    .overlay-scrim, .drawer-scrim { position:absolute; inset:0; background:rgba(10,12,14,0.6); backdrop-filter:blur(2px); z-index:50; display:flex; justify-content:flex-end; }\n    .overlay-scrim { align-items:stretch; }\n    .detail-panel { width:480px; max-width:92%; height:100%; background:var(--surface-2); border-left:1px solid var(--border); padding:28px; overflow-y:auto; position:relative; }\n    .tema-admin-panel { width:520px; }\n    .tema-admin-list { display:flex; flex-direction:column; gap:8px; margin:14px 0; max-height:320px; overflow-y:auto; }\n    .tema-row { display:flex; align-items:center; gap:8px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:9px; padding:8px 10px; }\n    .tema-row-name { flex:1; font-size:13px; }\n    .tema-row-count { font-size:11px; color:var(--text-dim); white-space:nowrap; }\n    .tema-rename-input { flex:1; background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:8px; color:var(--text); font-size:13px; padding:7px 10px; }\n    .tema-admin-add { display:flex; gap:8px; }\n    .new-tema-input { flex:1; background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:8px; color:var(--text); font-size:13px; padding:9px 12px; }\n    .modal-panel { margin:auto; width:460px; max-width:92%; max-height:88%; overflow-y:auto; background:var(--surface-2); border:1px solid var(--border); border-radius:18px; padding:28px; position:relative; align-self:center; }\n    .overlay-scrim:has(.modal-panel) { justify-content:center; align-items:center; }\n    .close-x { position:absolute; top:18px; right:18px; background:none; border:none; color:var(--text-faint); font-size:22px; cursor:pointer; line-height:1; }\n    .detail-head { display:flex; align-items:center; gap:10px; margin-bottom:14px; margin-top:6px; }\n    .title-input { flex:1; background:none; border:none; color:var(--text); font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:20px; font-weight:600; padding:4px 0; border-bottom:1px solid transparent; }\n    .title-input:focus { outline:none; border-bottom:1px solid var(--accent); }\n    .desc-input { width:100%; min-height:70px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:10px; color:var(--text-dim); font-size:13px; padding:10px 12px; margin-bottom:18px; resize:vertical; font-family:inherit; }\n    .detail-section { margin-bottom:20px; }\n    .section-label { font-size:11px; text-transform:uppercase; letter-spacing:0.05em; color:var(--text-faint); margin-bottom:8px; }\n    .pill-row { display:flex; gap:8px; flex-wrap:wrap; }\n    .pill-btn { font-size:12px; padding:7px 13px; border-radius:999px; border:1px solid var(--border); background:rgba(255,255,255,0.03); color:var(--text-dim); cursor:pointer; }\n    .pill-btn.is-active { background:var(--c); border-color:var(--c); color:#fff; font-weight:600; }\n    .chip-btn { font-size:12px; padding:6px 12px; border-radius:999px; border:1px solid var(--c); background:transparent; color:var(--c); cursor:pointer; }\n    .chip-btn.is-active { background:var(--c); color:#fff; }\n    .assignee-row { display:flex; gap:8px; }\n    .assignee-sel { flex:1; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:13px; padding:9px 10px; border-radius:9px; }\n    .add-person-inline { display:flex; gap:8px; margin-top:10px; align-items:center; flex-wrap:wrap; }\n    .add-person-inline .ap-name { flex:1; min-width:140px; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:12px; padding:7px 10px; border-radius:8px; }\n    .add-person-inline .claim-name { flex:1; min-width:140px; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:13px; padding:9px 10px; border-radius:8px; }\n    .id-picker .add-person-inline { justify-content:center; }\n    .ap-client { font-size:11px; color:var(--text-dim); display:flex; align-items:center; gap:4px; white-space:nowrap; }\n    .file-list, .comment-list { display:flex; flex-direction:column; gap:10px; margin-bottom:10px; }\n    .file-row { display:flex; justify-content:space-between; align-items:center; gap:10px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:10px; padding:10px 12px; }\n    .file-label { font-size:13px; font-weight:600; }\n    .file-meta { font-size:11px; color:var(--text-faint); margin-top:2px; }\n    .upload-btn { font-size:11px; padding:6px 10px; border-radius:999px; color:var(--c); border:1px dashed var(--c); cursor:pointer; white-space:nowrap; }\n    .comment-row { display:flex; gap:10px; }\n    .comment-body { flex:1; }\n    .comment-meta { display:flex; gap:8px; align-items:baseline; font-size:11px; color:var(--text-faint); flex-wrap:wrap; }\n    .comment-meta b { color:var(--text-dim); font-size:12px; }\n    .comment-text { font-size:13px; color:var(--text); margin-top:2px; line-height:1.5; }\n    .empty-hint { font-size:12px; color:var(--text-faint); font-style:italic; }\n    .inline-form { display:flex; gap:8px; }\n    .inline-form input { flex:1; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:12px; padding:9px 10px; border-radius:8px; }\n    .reply-inline { display:flex; gap:8px; margin-top:10px; padding-left:32px; }\n    .reply-input { flex:1; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:12px; padding:8px 10px; border-radius:8px; font-family:inherit; }\n    .reply-input:focus { outline:none; border-color:var(--accent); }\n    .delete-btn { margin-top:6px; width:100%; }\n\n    .modal-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:19px; font-weight:600; margin:6px 0 16px; }\n    .nt-title { width:100%; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:14px; padding:10px 12px; border-radius:9px; margin-bottom:10px; }\n    .nt-desc { width:100%; min-height:70px; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:13px; padding:10px 12px; border-radius:9px; margin-bottom:10px; font-family:inherit; resize:vertical; }\n    .modal-row { display:flex; gap:10px; margin-bottom:12px; flex-wrap:wrap; }\n    .modal-row select { flex:1; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:13px; padding:9px 10px; border-radius:9px; }\n    .nt-pub-row { gap:14px; }\n    .check-chip { font-size:12px; color:var(--text-dim); display:flex; align-items:center; gap:5px; }\n    .modal-actions { display:flex; justify-content:flex-end; gap:10px; margin-top:8px; }\n\n    .board-groupby-row { display:flex; gap:8px; padding:10px 24px; background:var(--surface-2); border-bottom:1px solid var(--border); flex-shrink:0; overflow-x:auto; }\n    .groupby-btn { flex-shrink:0; background:rgba(255,255,255,0.04); border:1px solid var(--border); color:var(--text-dim); font-size:12px; padding:7px 14px; border-radius:999px; cursor:pointer; }\n    .groupby-btn.is-active { background:var(--accent); border-color:var(--accent); color:#fff; }\n    .board-view { flex:1; overflow-x:auto; overflow-y:hidden; display:flex; gap:16px; padding:20px 24px; background:var(--bg); }\n    .board-lane { flex:0 0 268px; display:flex; flex-direction:column; background:radial-gradient(ellipse at 50% 0%, rgba(255,255,255,0.05), transparent 60%), var(--surface-2); border:1px solid var(--border); border-radius:18px; max-height:100%; transition:background .15s ease, border-color .15s ease; }\n    .board-lane.drag-over { border-color:var(--accent); background:radial-gradient(ellipse at 50% 0%, rgba(196,18,63,0.16), transparent 60%), var(--surface-2); }\n    .board-lane-header { display:flex; align-items:center; gap:8px; padding:14px 16px; border-bottom:1px solid var(--border); flex-shrink:0; }\n    .board-lane-dot { width:8px; height:8px; border-radius:50%; background:var(--c); flex-shrink:0; }\n    .board-lane-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:13.5px; font-weight:600; flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n    .board-lane-count { font-size:11px; color:var(--text-faint); background:rgba(255,255,255,0.06); border-radius:999px; padding:2px 8px; flex-shrink:0; }\n    .board-lane-body { flex:1; overflow-y:auto; padding:12px; display:flex; flex-direction:column; gap:10px; min-height:60px; }\n    .board-lane-empty { padding:16px 4px; }\n    .board-card { position:relative; width:100%; background:var(--surface); border:1px solid var(--border); border-left:3px solid var(--pcolor); border-radius:12px; padding:12px 14px; cursor:grab; box-shadow:0 1px 3px rgba(0,0,0,0.25); }\n    .board-card:hover { border-color:rgba(255,255,255,0.18); }\n    .board-card-source { opacity:0.3; }\n    .drag-ghost { position:fixed; z-index:300; box-shadow:0 16px 32px rgba(0,0,0,0.5); transform:rotate(-1.5deg) scale(1.02); cursor:grabbing; pointer-events:none; }\n\n    .kb-dropzone { border:2px dashed var(--border); border-radius:14px; padding:28px; text-align:center; margin-bottom:20px; transition:border-color .15s ease, background .15s ease; }\n    .kb-dropzone.drag-active { border-color:var(--accent); background:rgba(196,18,63,0.08); }\n    .kb-dropzone.is-busy { border-color:var(--text-faint); }\n    .kb-dz-text { font-size:13px; color:var(--text-dim); margin-bottom:10px; }\n    .kb-pick-label { display:inline-block; cursor:pointer; }\n    .kb-describe { display:flex; flex-direction:column; gap:10px; max-width:420px; margin:0 auto; text-align:left; }\n    .kb-describe input, .kb-describe textarea { background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:9px; color:var(--text); font-size:13px; padding:9px 12px; font-family:inherit; }\n    .kb-describe textarea { min-height:60px; resize:vertical; }\n    .kb-describe-actions { display:flex; justify-content:flex-end; gap:8px; }\n    .kb-desc-text { font-size:12px; color:var(--text-dim); margin-top:4px; line-height:1.5; }\n  `,\n\n  render (d2) {\n    const auth = d2.auth('auth')\n\n    if (!d2.state.ready) {\n      return `<div class=\"app\"><div class=\"loading-screen\">Carregando o painel de gestão do experimento IAP…</div></div>`\n    }\n\n    const accRec = d2.shared.get('iapUser', {}, { global: true }) || {}\n\n    const tasks = d2.data('tasks').records() || []\n    const people = d2.data('people').records() || []\n    const comments = d2.data('comments').records() || []\n    const files = d2.data('fileRequests').records() || []\n    const knowledge = d2.data('knowledge').records() || []\n    const temaRecords = (d2.data('iap-board-temasDataSet').records() || []).slice().sort((a, b) => (a.ordem || 0) - (b.ordem || 0))\n    TEMAS = computeTemaLayout(temaRecords, tasks)\n    const canvasSize = computeCanvasSize(TEMAS)\n    CANVAS_W = canvasSize.w\n    CANVAS_H = canvasSize.h\n\n    const uRaw0 = { email: accRec.email || '', name: accRec.name || '', role: accRec.role || '' }\n    const u = { email: uRaw0.email || (uRaw0.credentials && uRaw0.credentials.email) || '', name: uRaw0.name || (uRaw0.credentials && uRaw0.credentials.name) || '', role: uRaw0.role || (uRaw0.credentials && uRaw0.credentials.role) || '' }\n    const currentPerson = people.find(p => p.email && u.email && String(p.email).toLowerCase() === String(u.email).toLowerCase())\n\n    const view = d2.state.view || 'canvas'\n    const currentUser = { name: (currentPerson && currentPerson.name) || u.name || 'Visitante', role: u.role || 'cliente' }\n\n    return `\n      <div class=\"app\">\n        ${renderHeader(d2, tasks, currentUser)}\n        ${view === 'canvas' ? renderTemaNav(d2, tasks) : ''}\n        ${view === 'canvas' ? renderCanvas(d2, tasks, people, comments, files)\n          : view === 'board' ? renderBoard(d2, tasks, people, comments, files)\n          : view === 'list' ? renderList(d2, tasks, people)\n          : renderCentral(d2, tasks, comments, files, knowledge)}\n        ${renderDetail(d2, tasks, people, comments, files)}\n        ${renderNewTaskModal(d2)}\n        ${renderChangePasswordModal(d2)}\n        ${renderTemaAdminModal(d2, TEMAS, tasks)}\n      </div>`\n  },\n\n  mounted (d2, root) {\n    const tasksDs = d2.data('tasks')\n    const peopleDs = d2.data('people')\n    const commentsDs = d2.data('comments')\n    const filesDs = d2.data('fileRequests')\n    const knowledgeDs = d2.data('knowledge')\n    const temasDs = d2.data('iap-board-temasDataSet')\n\n    d2.setState({\n      view: 'canvas', zoom: 0.82, panX: 260, panY: 40, boardGroupBy: 'status',\n      selectedTaskId: null, newTaskOpen: false, addPersonOpen: false,\n      listFilters: {}, centralTab: 'files',\n      authMode: 'login', authError: null, authNotice: null, authBusy: false,\n      claimNewOpen: false, changePasswordOpen: false, cpError: null,\n      kbUploading: false, kbPending: null\n    })\n\n    const boot = async () => {\n      const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout após 8s')), 8000))\n      const work = (async () => {\n        await Promise.all([tasksDs.refresh(), peopleDs.refresh(), commentsDs.refresh(), filesDs.refresh(), knowledgeDs.refresh(), temasDs.refresh()])\n        await Promise.all([tasksDs.loadAll(), peopleDs.loadAll(), commentsDs.loadAll(), filesDs.loadAll(), knowledgeDs.loadAll(), temasDs.loadAll()])\n        d2.setState({ ready: true, bootError: null })\n      })()\n      try {\n        await Promise.race([work, timeout])\n      } catch (err) {\n        d2.setState({ ready: true, bootError: (err && err.message) || 'Não foi possível carregar os dados.' })\n      }\n    }\n    boot()\n\n    const currentTask = (id) => (tasksDs.records() || []).find(t => String(t._id) === String(id))\n    const who = () => {\n      const u = d2.shared.get('iapUser', {}, { global: true }) || {}\n      const p = (peopleDs.records() || []).find(x => x.email && u.email && String(x.email).toLowerCase() === String(u.email).toLowerCase())\n      return (p && p.name) || u.name || u.email || 'Cliente'\n    }\n    const refreshTasks = async () => { await tasksDs.refresh(); await tasksDs.loadAll(); d2.refresh() }\n    const refreshComments = async () => { await commentsDs.refresh(); await commentsDs.loadAll(); d2.refresh() }\n    const refreshFiles = async () => { await filesDs.refresh(); await filesDs.loadAll(); d2.refresh() }\n    const refreshPeople = async () => { await peopleDs.refresh(); await peopleDs.loadAll(); d2.refresh() }\n    const refreshKnowledge = async () => { await knowledgeDs.refresh(); await knowledgeDs.loadAll(); d2.refresh() }\n    const refreshTemas = async () => { await temasDs.refresh(); await temasDs.loadAll(); d2.refresh() }\n\n    const saveTaskField = async (id, field, value) => {\n      const t = currentTask(id)\n      if (!t) return\n      const oldValue = t[field]\n      const record = Object.assign({}, t, { [field]: value, updated_at: nowIso(), updated_by: who() })\n      const activity = buildActivity(t, 'update', field, oldValue, value, who())\n      // optimistic: patch in-memory record + mark saving, re-render immediately\n      Object.assign(t, record, { _savingField: field })\n      d2.refresh()\n      try {\n        await d2.rush('saveTask').invoke({ record, activity })\n        delete t._savingField\n        await refreshTasks()\n      } catch (err) {\n        d2.log('save task field failed', err)\n        t[field] = oldValue\n        delete t._savingField\n        d2.setState({ bootError: null })\n        d2.refresh()\n      }\n    }\n\n    // ---- auth: login / signup / logout ----\n    d2.on('[data-act=\"auth-mode\"]', 'click', (e, el) => d2.setState({ authMode: el.dataset.value, authError: null, authNotice: null }))\n    d2.on('[data-act=\"do-login\"]', 'click', async () => {\n      const email = (d2.$('.auth-email') || {}).value || ''\n      const password = (d2.$('.auth-password') || {}).value || ''\n      if (!email || !password) { d2.setState({ authError: 'Preencha e-mail e senha.' }); return }\n      d2.setState({ authBusy: true, authError: null })\n      try {\n        await d2.auth('auth').login({ credentials: { email, password } })\n        d2.setState({ authBusy: false })\n        boot()\n      } catch (err) {\n        d2.setState({ authBusy: false, authError: 'E-mail ou senha inválidos.' })\n      }\n    })\n    d2.on('[data-act=\"do-signup\"]', 'click', async () => {\n      const email = (d2.$('.auth-email') || {}).value || ''\n      const password = (d2.$('.auth-password') || {}).value || ''\n      const confirm = (d2.$('.auth-password-confirm') || {}).value || ''\n      const name = (d2.$('.auth-name') || {}).value || ''\n      const internal = (d2.$('.auth-internal') || {}).checked\n      if (!email || !password || !name) { d2.setState({ authError: 'Preencha nome, e-mail e senha.' }); return }\n      if (password !== confirm) { d2.setState({ authError: 'As senhas não coincidem.' }); return }\n      const role = email.trim().toLowerCase() === 'gui@data2.app' ? 'tech-lead' : (internal ? 'interno' : 'cliente')\n      d2.setState({ authBusy: true, authError: null })\n      try {\n        const a = d2.auth('auth')\n        d2.field(a.raw, 'credentials.email').set(email)\n        d2.field(a.raw, 'credentials.password').set(password)\n        d2.field(a.raw, 'credentials.name').set(name)\n        d2.field(a.raw, 'credentials.role').set(role)\n        d2.field(a.raw, 'registration.password').set(confirm)\n        await a.register()\n        d2.setState({ authBusy: false, authMode: 'login', authNotice: 'Conta criada! Se não entrou automaticamente, faça login com sua senha.' })\n        boot()\n      } catch (err) {\n        d2.setState({ authBusy: false, authError: 'Não foi possível criar a conta. Verifique os dados e tente novamente.' })\n      }\n    })\n    d2.on('[data-act=\"do-logout\"]', 'click', async () => {\n      await d2.auth('auth').logout()\n      window.location.reload()\n    })\n\n    // ---- claim / link a person record to the logged-in account ----\n    d2.on('[data-act=\"open-claim-new\"]', 'click', () => d2.setState({ claimNewOpen: !d2.state.claimNewOpen }))\n    d2.on('[data-act=\"claim-person\"]', 'click', async (e, el) => {\n      const authR1 = d2.auth('auth')\n      const sessR1 = authR1.session || {}\n      const uRaw1 = sessR1.user || sessR1.credentials || sessR1 || authR1.user || {}\n      const u = { email: uRaw1.email || (uRaw1.credentials && uRaw1.credentials.email) || '', name: uRaw1.name || (uRaw1.credentials && uRaw1.credentials.name) || '', role: uRaw1.role || (uRaw1.credentials && uRaw1.credentials.role) || '' }\n      const p = (peopleDs.records() || []).find(x => String(x._id) === String(el.dataset.id))\n      if (!p) return\n      const record = Object.assign({}, p, { email: u.email || '' })\n      await d2.rush('savePerson').invoke({ record })\n      await refreshPeople()\n    })\n    d2.on('[data-act=\"claim-new-submit\"]', 'click', async () => {\n      const authR1 = d2.auth('auth')\n      const sessR1 = authR1.session || {}\n      const uRaw1 = sessR1.user || sessR1.credentials || sessR1 || authR1.user || {}\n      const u = { email: uRaw1.email || (uRaw1.credentials && uRaw1.credentials.email) || '', name: uRaw1.name || (uRaw1.credentials && uRaw1.credentials.name) || '', role: uRaw1.role || (uRaw1.credentials && uRaw1.credentials.role) || '' }\n      const nameEl = d2.$('.claim-name')\n      const name = nameEl ? nameEl.value.trim() : ''\n      if (!name) return\n      const isInternal = u.role === 'interno' || u.role === 'tech-lead'\n      const record = {\n        name, color: nextPersonColor(peopleDs.records() || []),\n        is_client: !isInternal, email: u.email || '', role: isInternal ? 'squad' : 'cliente'\n      }\n      await d2.rush('savePerson').invoke({ record })\n      await refreshPeople()\n      d2.setState({ claimNewOpen: false })\n    })\n\n    // ---- gerenciar temas ----\n    d2.on('[data-act=\"open-tema-admin\"]', 'click', () => d2.setState({ temaAdminOpen: true, temaAdminError: null, temaEditingId: null }))\n    d2.on('[data-act=\"close-tema-admin\"]', 'click', (e, el) => { if (e.target === el) d2.setState({ temaAdminOpen: false, temaEditingId: null }) })\n    d2.on('[data-act=\"add-tema\"]', 'click', async () => {\n      const input = d2.$('.new-tema-input')\n      const nome = input ? input.value.trim() : ''\n      if (!nome) return\n      const existing = (temasDs.records() || [])\n      if (existing.find(t => String(t.nome).toLowerCase() === nome.toLowerCase())) {\n        d2.setState({ temaAdminError: 'Já existe um tema com esse nome.' }); return\n      }\n      const maxOrdem = existing.reduce((m, t) => Math.max(m, t.ordem || 0), 0)\n      const record = { nome, ordem: maxOrdem + 1, created_at: nowIso(), updated_at: nowIso(), updated_by: who() }\n      const act = { entity: 'tema', entity_id: '', entity_title: nome, action: 'create', by: who(), at: nowIso() }\n      await d2.rush('iap_board_save_tema').invoke({ record, activity: act })\n      if (input) input.value = ''\n      d2.setState({ temaAdminError: null })\n      await refreshTemas()\n    })\n    d2.on('[data-act=\"start-tema-rename\"]', 'click', (e, el) => d2.setState({ temaEditingId: el.dataset.id, temaAdminError: null }))\n    d2.on('[data-act=\"cancel-tema-rename\"]', 'click', () => d2.setState({ temaEditingId: null }))\n    d2.on('[data-act=\"save-tema-rename\"]', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const input = d2.$('.tema-rename-input[data-id=\"' + id + '\"]')\n      const novoNome = input ? input.value.trim() : ''\n      if (!novoNome) return\n      const t = (temasDs.records() || []).find(x => String(x._id) === String(id))\n      if (!t) return\n      const oldNome = t.nome\n      const record = Object.assign({}, t, { nome: novoNome, updated_at: nowIso(), updated_by: who() })\n      const act = { entity: 'tema', entity_id: id, entity_title: novoNome, action: 'rename', field: 'nome', old_value: oldNome, new_value: novoNome, by: who(), at: nowIso() }\n      await d2.rush('iap_board_save_tema').invoke({ record, activity: act })\n      if (oldNome !== novoNome) {\n        const affected = (tasksDs.records() || []).filter(x => x.tema === oldNome)\n        for (const task of affected) { await saveTaskField(task._id, 'tema', novoNome) }\n      }\n      d2.setState({ temaEditingId: null })\n      await refreshTemas()\n      await refreshTasks()\n    })\n    d2.on('[data-act=\"delete-tema\"]', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const count = parseInt(el.dataset.count || '0', 10)\n      if (count > 0) {\n        d2.setState({ temaAdminError: `Esse tema tem ${count} tarefa(s). Renomeie as tarefas para outro tema antes de apagar.` })\n        return\n      }\n      const ok = await d2.dialog.confirm('Apagar esse tema?')\n      if (!ok) return\n      const t = (temasDs.records() || []).find(x => String(x._id) === String(id))\n      const act = t ? { entity: 'tema', entity_id: id, entity_title: t.nome, action: 'deleted', by: who(), at: nowIso() } : null\n      await d2.rush('iap_board_delete_tema').invoke({ _id: id, activity: act })\n      await refreshTemas()\n    })\n\n    // ---- change password ----\n    d2.on('[data-act=\"open-change-password\"]', 'click', () => d2.setState({ changePasswordOpen: true, cpError: null }))\n    d2.on('[data-act=\"close-change-password\"]', 'click', (e, el) => { if (e.target === el) d2.setState({ changePasswordOpen: false }) })\n    d2.on('[data-act=\"submit-change-password\"]', 'click', async () => {\n      const cur = (d2.$('.cp-current') || {}).value || ''\n      const nw = (d2.$('.cp-new') || {}).value || ''\n      const cf = (d2.$('.cp-confirm') || {}).value || ''\n      if (!cur || !nw) { d2.setState({ cpError: 'Preencha todos os campos.' }); return }\n      if (nw !== cf) { d2.setState({ cpError: 'As senhas não coincidem.' }); return }\n      try {\n        const a = d2.auth('auth')\n        d2.field(a.raw, 'previous.password').set(cur)\n        d2.field(a.raw, 'credentials.password').set(nw)\n        d2.field(a.raw, 'registration.password').set(cf)\n        await a.changePassword()\n        d2.setState({ changePasswordOpen: false, cpError: null })\n      } catch (err) {\n        d2.setState({ cpError: 'Não foi possível alterar a senha. Confira a senha atual.' })\n      }\n    })\n\n    // ---- panning (drag empty canvas background) ----\n    let dragging = false, dragMoved = false, startX = 0, startY = 0, startPanX = 0, startPanY = 0\n    d2.on(root, 'mousedown', (e) => {\n      if ((d2.state.view || 'canvas') !== 'canvas') return\n      if (d2.state.selectedTaskId || d2.state.newTaskOpen) return\n      if (e.target.closest && e.target.closest('.no-pan')) return\n      dragging = true; dragMoved = false\n      startX = e.clientX; startY = e.clientY\n      startPanX = d2.state.panX || 0; startPanY = d2.state.panY || 0\n      const vp = d2.$('.viewport'); if (vp) vp.classList.add('is-grabbing')\n    })\n    d2.on(window, 'mousemove', (e) => {\n      if (!dragging) return\n      const dx = e.clientX - startX, dy = e.clientY - startY\n      if (Math.abs(dx) + Math.abs(dy) > 4) dragMoved = true\n      const layer = d2.$('.canvas-layer')\n      if (layer) layer.style.transform = `translate(${startPanX + dx}px, ${startPanY + dy}px) scale(${d2.state.zoom || 0.82})`\n    })\n    d2.on(window, 'mouseup', (e) => {\n      if (!dragging) return\n      dragging = false\n      const vp = d2.$('.viewport'); if (vp) vp.classList.remove('is-grabbing')\n      const dx = e.clientX - startX, dy = e.clientY - startY\n      if (dragMoved) d2.setState({ panX: startPanX + dx, panY: startPanY + dy })\n      root.__iapDragMoved = dragMoved\n    })\n\n    // ---- board: grouping switch ----\n    d2.on('[data-act=\"set-groupby\"]', 'click', (e, el) => d2.setState({ boardGroupBy: el.dataset.value }))\n\n    // ---- board: drag a card between lanes ----\n    let boardDrag = null\n    d2.on(root, 'mousedown', (e) => {\n      if (d2.state.view !== 'board') return\n      const cardEl = e.target.closest && e.target.closest('.board-card')\n      if (!cardEl) return\n      e.preventDefault()\n      const rect = cardEl.getBoundingClientRect()\n      const ghost = cardEl.cloneNode(true)\n      ghost.classList.add('drag-ghost')\n      ghost.style.position = 'fixed'\n      ghost.style.left = rect.left + 'px'\n      ghost.style.top = rect.top + 'px'\n      ghost.style.width = rect.width + 'px'\n      ghost.style.margin = '0'\n      root.appendChild(ghost)\n      cardEl.classList.add('board-card-source')\n      const laneRects = Array.prototype.slice.call(d2.$$('.board-lane')).map(el => ({ el, key: el.dataset.laneKey, rect: el.getBoundingClientRect() }))\n      boardDrag = {\n        taskId: cardEl.dataset.dragTask, groupBy: cardEl.dataset.groupBy, sourceLane: cardEl.dataset.sourceLane,\n        ghost, cardEl, laneRects, moved: false,\n        offsetX: e.clientX - rect.left, offsetY: e.clientY - rect.top\n      }\n    })\n    d2.on(window, 'mousemove', (e) => {\n      if (!boardDrag) return\n      boardDrag.moved = true\n      boardDrag.ghost.style.left = (e.clientX - boardDrag.offsetX) + 'px'\n      boardDrag.ghost.style.top = (e.clientY - boardDrag.offsetY) + 'px'\n      const hit = boardDrag.laneRects.find(l => e.clientX >= l.rect.left && e.clientX <= l.rect.right && e.clientY >= l.rect.top && e.clientY <= l.rect.bottom)\n      boardDrag.laneRects.forEach(l => l.el.classList.toggle('drag-over', !!hit && l.key === hit.key))\n    })\n    d2.on(window, 'mouseup', async (e) => {\n      if (!boardDrag) return\n      const drag = boardDrag\n      boardDrag = null\n      drag.ghost.remove()\n      drag.cardEl.classList.remove('board-card-source')\n      drag.laneRects.forEach(l => l.el.classList.remove('drag-over'))\n      root.__iapDragMoved = drag.moved\n      if (!drag.moved) return\n      const hit = drag.laneRects.find(l => e.clientX >= l.rect.left && e.clientX <= l.rect.right && e.clientY >= l.rect.top && e.clientY <= l.rect.bottom)\n      if (!hit || hit.key === drag.sourceLane) return\n      const t = currentTask(drag.taskId)\n      if (!t) return\n      const patch = applyDropValue(drag.groupBy, t, drag.sourceLane, hit.key)\n      if (patch) await saveTaskField(drag.taskId, patch.field, patch.value)\n    })\n\n    // ---- keyboard zoom ----\n    d2.on(window, 'keydown', (e) => {\n      const tag = (e.target && e.target.tagName) || ''\n      if (tag === 'INPUT' || tag === 'TEXTAREA') return\n      if ((d2.state.view || 'canvas') !== 'canvas') return\n      if (e.key === '+' || e.key === '=') d2.setState({ zoom: clamp((d2.state.zoom || 0.82) + 0.1, 0.5, 1.3) })\n      if (e.key === '-') d2.setState({ zoom: clamp((d2.state.zoom || 0.82) - 0.1, 0.5, 1.3) })\n    })\n\n    d2.on('[data-act=\"zoom-in\"]', 'click', () => d2.setState({ zoom: clamp((d2.state.zoom || 0.82) + 0.1, 0.5, 1.3) }))\n    d2.on('[data-act=\"zoom-out\"]', 'click', () => d2.setState({ zoom: clamp((d2.state.zoom || 0.82) - 0.1, 0.5, 1.3) }))\n    d2.on('[data-act=\"zoom-fit\"]', 'click', () => {\n      const fitZoom = clamp(Math.min(VIEWPORT_W / CANVAS_W, VIEWPORT_H / CANVAS_H), 0.25, 1.3)\n      const panX = (VIEWPORT_W - CANVAS_W * fitZoom) / 2\n      const panY = (VIEWPORT_H - CANVAS_H * fitZoom) / 2\n      d2.setState({ zoom: fitZoom, panX, panY })\n    })\n\n    d2.on('[data-act=\"jump\"]', 'click', (e, el) => {\n      const tema = el.dataset.tema\n      const cl = temaMeta(tema)\n      const zoom = d2.state.zoom || 0.82\n      const panX = VIEWPORT_W / 2 - (cl.x + cl.w / 2) * zoom\n      const panY = VIEWPORT_H / 2 - (cl.y + cl.h / 2) * zoom\n      d2.setState({ panX, panY })\n    })\n\n    d2.on('[data-view]', 'click', (e, el) => {\n      const v = el.dataset.view\n      d2.setState({ view: v })\n      if (v === 'central') { refreshFiles(); refreshComments(); refreshKnowledge() }\n    })\n\n    d2.on('[data-act=\"new-task\"]', 'click', () => d2.setState({ newTaskOpen: true }))\n    d2.on('[data-act=\"close-new-task\"]', 'click', (e, el) => { if (e.target === el) d2.setState({ newTaskOpen: false }) })\n    d2.on('[data-act=\"submit-new-task\"]', 'click', async (e, el) => {\n      const title = (d2.$('.nt-title') || {}).value\n      if (!title || !title.trim()) return\n      el.disabled = true\n      try {\n        const desc = (d2.$('.nt-desc') || {}).value || ''\n        const tema = (d2.$('.nt-tema') || {}).value || (TEMAS[0] && TEMAS[0].tema) || ''\n        const priority = (d2.$('.nt-priority') || {}).value || 'media'\n        const publicos = Array.prototype.slice.call(d2.$('.nt-publico')).filter(c => c.checked).map(c => c.value)\n        const record = {\n          title: title.trim(), description: desc, status: 'a_fazer', priority, publicos, tema,\n          assignee_id: null, pos_x: 0, pos_y: 0, created_at: nowIso(), updated_at: nowIso(), updated_by: who()\n        }\n        const activity = buildActivity(record, 'create', '', '', title.trim(), who())\n        await d2.rush('saveTask').invoke({ record, activity })\n        d2.setState({ newTaskOpen: false })\n        await refreshTasks()\n      } catch (err) { d2.log('create task failed', err) }\n      el.disabled = false\n    })\n\n    d2.on('[data-act=\"open-task\"]', 'click', (e, el) => {\n      if (root.__iapDragMoved) { root.__iapDragMoved = false; return }\n      d2.setState({ selectedTaskId: el.dataset.id, addPersonOpen: false })\n    })\n    d2.on('[data-act=\"close-overlay\"]', 'click', (e, el) => { if (e.target === el) d2.setState({ selectedTaskId: null, addPersonOpen: false }) })\n\n    d2.on('[data-act=\"edit-title\"]', 'blur', (e, el) => {\n      const id = el.dataset.id\n      const t = currentTask(id)\n      if (t && el.value.trim() && el.value.trim() !== t.title) saveTaskField(id, 'title', el.value.trim())\n    })\n    d2.on('[data-act=\"edit-desc\"]', 'blur', (e, el) => {\n      const id = el.dataset.id\n      const t = currentTask(id)\n      if (t && el.value !== (t.description || '')) saveTaskField(id, 'description', el.value)\n    })\n\n    d2.on('[data-act=\"set-status\"]', 'click', (e, el) => saveTaskField(d2.state.selectedTaskId, 'status', el.dataset.value))\n    d2.on('[data-act=\"set-priority\"]', 'click', (e, el) => saveTaskField(d2.state.selectedTaskId, 'priority', el.dataset.value))\n    d2.on('[data-act=\"toggle-publico\"]', 'click', (e, el) => {\n      const t = currentTask(d2.state.selectedTaskId)\n      if (!t) return\n      const cur = (t.publicos || []).slice()\n      const v = el.dataset.value\n      const next = cur.includes(v) ? cur.filter(x => x !== v) : cur.concat([v])\n      saveTaskField(d2.state.selectedTaskId, 'publicos', next)\n    })\n    d2.on('[data-act=\"set-assignee\"]', 'change', (e, el) => saveTaskField(d2.state.selectedTaskId, 'assignee_id', el.value || null))\n\n    d2.on('[data-act=\"open-add-person\"]', 'click', () => d2.setState({ addPersonOpen: !d2.state.addPersonOpen }))\n    d2.on('[data-act=\"save-person\"]', 'click', async (e, el) => {\n      const nameEl = d2.$('.ap-name')\n      const clientEl = d2.$('[data-act=\"ap-client\"]')\n      const name = nameEl ? nameEl.value.trim() : ''\n      if (!name) return\n      el.disabled = true\n      try {\n        const record = { name, color: nextPersonColor(peopleDs.records() || []), is_client: clientEl ? !!clientEl.checked : true }\n        const res = await d2.rush('savePerson').invoke({ record })\n        await refreshPeople()\n        const newId = res && res.data && res.data.id\n        if (newId) await saveTaskField(d2.state.selectedTaskId, 'assignee_id', newId)\n        d2.setState({ addPersonOpen: false })\n      } catch (err) { d2.log('save person failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"request-file\"]', 'click', async (e, el) => {\n      const input = d2.$('.fr-label')\n      const label = input ? input.value.trim() : ''\n      if (!label) return\n      el.disabled = true\n      const record = { task_id: el.dataset.id, label, status: 'pendente', requested_by: who(), requested_at: nowIso(), file_url: null, file_name: null }\n      try { await d2.rush('saveFileRequest').invoke({ record }) } catch (err) { d2.log('request file failed', err) }\n      el.disabled = false\n      if (input) input.value = ''\n      await refreshFiles()\n    })\n    d2.on('[data-act=\"upload-file\"]', 'change', async (e, el) => {\n      const file = el.files && el.files[0]\n      if (!file) return\n      const frId = el.dataset.fileId\n      try {\n        const uploaded = await d2.drive('drive').uploadFromFiles([file])\n        const up0 = uploaded && uploaded[0]\n        const fileUrl = up0 && (up0.fileUrl || up0.url || up0.publicUrl || up0.href || up0.location)\n        const fr = (filesDs.records() || []).find(f => String(f._id) === String(frId))\n        if (!fr) return\n        const record = Object.assign({}, fr, { status: 'recebido', file_url: fileUrl || null, file_name: file.name })\n        await d2.rush('saveFileRequest').invoke({ record })\n        await refreshFiles()\n      } catch (err) { d2.log('upload failed', err) }\n    })\n\n    d2.on('[data-act=\"delete-file-request\"]', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const ok = await d2.dialog.confirm('Apagar esse arquivo/solicitação?')\n      if (!ok) return\n      el.disabled = true\n      const fr = (filesDs.records() || []).find(f => String(f._id) === String(id))\n      const act = fr ? { entity: 'file_request', entity_id: id, entity_title: fr.label, action: 'deleted', by: who(), at: nowIso() } : null\n      try {\n        await d2.rush('iap_board_delete_file_request').invoke({ _id: id, activity: act })\n        await refreshFiles()\n      } catch (err) { d2.log('delete file request failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"delete-knowledge\"]', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const ok = await d2.dialog.confirm('Apagar esse documento da base de conhecimento?')\n      if (!ok) return\n      el.disabled = true\n      const kb = (knowledgeDs.records() || []).find(k => String(k._id) === String(id))\n      const act = kb ? { entity: 'knowledge', entity_id: id, entity_title: kb.title, action: 'deleted', by: who(), at: nowIso() } : null\n      try {\n        await d2.rush('iap_board_delete_knowledge').invoke({ _id: id, activity: act })\n        await refreshKnowledge()\n      } catch (err) { d2.log('delete knowledge failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"toggle-reply\"]', 'click', (e, el) => {\n      const id = el.dataset.id\n      d2.setState({ replyOpenId: d2.state.replyOpenId === id ? null : id })\n    })\n\n    d2.on('[data-act=\"send-reply\"]', 'click', async (e, el) => {\n      const parentId = el.dataset.parent\n      const taskId = el.dataset.task || null\n      const input = el.parentElement ? el.parentElement.querySelector('.reply-input') : null\n      const body = input ? input.value.trim() : ''\n      if (!body) return\n      el.disabled = true\n      const record = { task_id: taskId, reply_to: parentId, author_name: who(), body, created_at: nowIso() }\n      try {\n        await d2.rush('saveComment').invoke({ record })\n        d2.setState({ replyOpenId: null })\n        await refreshComments()\n      } catch (err) { d2.log('reply failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"send-comment\"]', 'click', async (e, el) => {\n      const input = d2.$('.comment-input')\n      const body = input ? input.value.trim() : ''\n      if (!body) return\n      el.disabled = true\n      try {\n        await d2.rush('saveComment').invoke({ record: { task_id: el.dataset.id, author_id: null, author_name: who(), body, created_at: nowIso() } })\n        if (input) input.value = ''\n        await refreshComments()\n      } catch (err) { d2.log('send comment failed', err) }\n      el.disabled = false\n    })\n\n    d2.on('[data-act=\"delete-task\"]', 'click', async (e, el) => {\n      const t = currentTask(el.dataset.id)\n      if (!t) return\n      const ok = await d2.dialog.confirm(`Excluir a tarefa \"${t.title}\"? Essa ação não pode ser desfeita.`)\n      if (!ok) return\n      el.disabled = true\n      try {\n        const activity = buildActivity(t, 'delete', '', t.title, '', who())\n        await d2.rush('deleteTask').invoke({ _id: el.dataset.id, activity })\n        d2.setState({ selectedTaskId: null })\n        await refreshTasks()\n      } catch (err) { d2.log('delete task failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"filter\"]', 'change', (e, el) => {\n      const f = Object.assign({}, d2.state.listFilters || {})\n      f[el.dataset.field] = el.value\n      d2.setState({ listFilters: f })\n    })\n    d2.on('[data-act=\"filter-search\"]', 'input', (e, el) => {\n      const f = Object.assign({}, d2.state.listFilters || {})\n      f.q = el.value\n      d2.setState({ listFilters: f })\n    })\n    d2.on('[data-act=\"clear-filters\"]', 'click', () => d2.setState({ listFilters: {} }))\n\n    // ---- Central hub ----\n    d2.on('[data-act=\"central-tab\"]', 'click', (e, el) => {\n      const v = el.dataset.value\n      d2.setState({ centralTab: v })\n      if (v === 'files') refreshFiles()\n      else if (v === 'comments') refreshComments()\n      else if (v === 'knowledge') refreshKnowledge()\n    })\n    d2.on('[data-act=\"jump-to-task\"]', 'click', (e, el) => {\n      const id = el.dataset.id\n      if (!id) return\n      d2.setState({ view: 'canvas', selectedTaskId: id })\n    })\n    d2.on('[data-act=\"hub-request-file\"]', 'click', async (e, el) => {\n      const taskSel = d2.$('.hub-task-sel-file')\n      const labelEl = d2.$('.hub-fr-label')\n      const label = labelEl ? labelEl.value.trim() : ''\n      if (!label) return\n      el.disabled = true\n      const record = { task_id: (taskSel && taskSel.value) || null, label, status: 'pendente', requested_by: who(), requested_at: nowIso(), file_url: null, file_name: null }\n      try { await d2.rush('saveFileRequest').invoke({ record }) } catch (err) { d2.log('hub request file failed', err) }\n      el.disabled = false\n      if (labelEl) labelEl.value = ''\n      await refreshFiles()\n    })\n    d2.on('[data-act=\"hub-send-comment\"]', 'click', async (e, el) => {\n      const taskSel = d2.$('.hub-task-sel-comment')\n      const input = d2.$('.hub-comment-input')\n      const body = input ? input.value.trim() : ''\n      if (!body) return\n      el.disabled = true\n      const record = { task_id: (taskSel && taskSel.value) || null, author_id: null, author_name: who(), body, created_at: nowIso() }\n      try {\n        await d2.rush('saveComment').invoke({ record })\n        if (input) input.value = ''\n        await refreshComments()\n      } catch (err) { d2.log('hub send comment failed', err) }\n      el.disabled = false\n    })\n\n    // ---- knowledge base drop zone ----\n    const startKbUpload = async (file) => {\n      if (!file) return\n      d2.setState({ kbUploading: true })\n      try {\n        const uploaded = await d2.drive('drive').uploadFromFiles([file])\n        const up0 = uploaded && uploaded[0]\n        const fileUrl = up0 && (up0.fileUrl || up0.url || up0.publicUrl || up0.href || up0.location)\n        d2.setState({ kbUploading: false, kbPending: { file_url: fileUrl || null, file_name: file.name } })\n      } catch (err) {\n        d2.setState({ kbUploading: false })\n        d2.log('kb upload failed', err)\n      }\n    }\n    d2.on('.kb-dropzone', 'dragover', (e, el) => { e.preventDefault(); el.classList.add('drag-active') })\n    d2.on('.kb-dropzone', 'dragleave', (e, el) => { el.classList.remove('drag-active') })\n    d2.on('.kb-dropzone', 'drop', async (e, el) => {\n      e.preventDefault()\n      el.classList.remove('drag-active')\n      const file = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0]\n      await startKbUpload(file)\n    })\n    d2.on('.kb-file-input', 'change', async (e, el) => {\n      const file = el.files && el.files[0]\n      await startKbUpload(file)\n      el.value = ''\n    })\n    d2.on('[data-act=\"kb-cancel\"]', 'click', () => d2.setState({ kbPending: null }))\n    d2.on('[data-act=\"kb-submit\"]', 'click', async () => {\n      const titleEl = d2.$('.kb-title')\n      const descEl = d2.$('.kb-desc-input')\n      const title = titleEl ? titleEl.value.trim() : ''\n      if (!title) return\n      const pending = d2.state.kbPending\n      if (!pending) return\n      const record = {\n        file_url: pending.file_url, file_name: pending.file_name,\n        title, description: descEl ? descEl.value.trim() : '',\n        tags: [], uploaded_by: who(), uploaded_at: nowIso()\n      }\n      await d2.rush('saveKnowledge').invoke({ record })\n      d2.setState({ kbPending: null })\n      await refreshKnowledge()\n    })\n  }\n}\n"}},"tasks":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"tasks"}}}},"people":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"people"}}}},"comments":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"comments"}}}},"fileRequests":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"fileRequests"}}}},"knowledge":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"knowledge"}}}},"saveTask":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"saveTask"}}}},"deleteTask":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"deleteTask"}}}},"saveComment":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"saveComment"}}}},"saveFileRequest":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"saveFileRequest"}}}},"savePerson":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"savePerson"}}}},"saveKnowledge":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"saveKnowledge"}}}},"drive":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"drive"}}}},"auth":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"auth"}}}}}}}},"__d2PrevCode":{"code":"const STATUS_META = {\n  a_fazer:      { label: 'A fazer',       color: '#8792A2', bg: 'rgba(135,146,162,0.16)' },\n  em_andamento: { label: 'Em andamento',  color: '#E8A93A', bg: 'rgba(232,169,58,0.20)' },\n  on_hold:      { label: 'Em pausa',      color: '#B98A5A', bg: 'rgba(185,138,90,0.20)' },\n  feito:        { label: 'Concluído',     color: '#4C9A6B', bg: 'rgba(76,154,107,0.20)' }\n}\nconst STATUS_ORDER = ['a_fazer', 'em_andamento', 'on_hold', 'feito']\n\nconst PRIORITY_META = {\n  baixa: { label: 'Baixa', color: '#7C8593' },\n  media: { label: 'Média', color: '#E8A93A' },\n  alta:  { label: 'Alta',  color: '#C4123F' }\n}\nconst PRIORITY_ORDER = ['baixa', 'media', 'alta']\n\nconst PUBLICO_META = {\n  GN:     { label: 'GN',     color: '#2E6B8C' },\n  GV:     { label: 'GV',     color: '#4C7A5A' },\n  LIDER:  { label: 'Líder',  color: '#C97A1A' },\n  NATURA: { label: 'Natura', color: '#7A5C99' }\n}\nconst PUBLICO_ORDER = ['GN', 'GV', 'LIDER', 'NATURA']\n\nconst PERSON_PALETTE = ['#C4123F', '#2E6B8C', '#C97A1A', '#4C7A5A', '#7A5C99', '#B23A6B', '#3D8C8C']\n\nconst LABEL_H = 60\n\nlet TEMAS = []\nlet CANVAS_W = 3000\nlet CANVAS_H = 1980\n\nfunction computeTemaLayout (temaRecords, tasks) {\n  const COLS = 3\n  const COL_W = 620\n  const GAP = 40\n  const MARGIN = 60\n  const CARDS_PER_ROW = 2\n  const colX = []\n  for (let i = 0; i < COLS; i++) colX.push(MARGIN + i * (COL_W + GAP))\n  const colY = colX.map(() => MARGIN)\n  const out = temaRecords.map((rec, idx) => {\n    const n = tasks.filter(x => x.tema === rec.nome).length\n    const rows = Math.max(1, Math.ceil(n / CARDS_PER_ROW))\n    const h = LABEL_H + 20 + rows * 160\n    const col = idx % COLS\n    const item = { tema: rec.nome, _id: rec._id, ordem: (rec.ordem || 0), x: colX[col], y: colY[col], w: COL_W, h: h }\n    colY[col] += h + GAP\n    return item\n  })\n  return out\n}\n\nfunction computeCanvasSize (temasList) {\n  let maxX = 1600\n  let maxY = 1200\n  temasList.forEach(t => {\n    maxX = Math.max(maxX, t.x + t.w + 200)\n    maxY = Math.max(maxY, t.y + t.h + 200)\n  })\n  return { w: maxX, h: maxY }\n}\nconst VIEWPORT_W = 1600\nconst VIEWPORT_H = 888\n\nconst GROUPBY_OPTIONS = [\n  { key: 'tema', label: 'Tema' },\n  { key: 'status', label: 'Status' },\n  { key: 'priority', label: 'Prioridade' },\n  { key: 'assignee', label: 'Responsável' },\n  { key: 'publico', label: 'Público' }\n]\n\nfunction getLanes (groupBy, people) {\n  if (groupBy === 'tema') return TEMAS.map(t => ({ key: t.tema, label: t.tema, color: '#8792A2' }))\n  if (groupBy === 'status') return STATUS_ORDER.map(s => ({ key: s, label: STATUS_META[s].label, color: STATUS_META[s].color }))\n  if (groupBy === 'priority') return PRIORITY_ORDER.map(p => ({ key: p, label: PRIORITY_META[p].label, color: PRIORITY_META[p].color }))\n  if (groupBy === 'assignee') return people.map(p => ({ key: String(p._id), label: p.name, color: p.color })).concat([{ key: '', label: 'Sem responsável', color: '#6E757C' }])\n  if (groupBy === 'publico') return PUBLICO_ORDER.map(p => ({ key: p, label: PUBLICO_META[p].label, color: PUBLICO_META[p].color })).concat([{ key: '__none__', label: 'Sem público definido', color: '#6E757C' }])\n  return []\n}\n\nfunction taskInLane (groupBy, task, laneKey) {\n  if (groupBy === 'tema') return task.tema === laneKey\n  if (groupBy === 'status') return task.status === laneKey\n  if (groupBy === 'priority') return task.priority === laneKey\n  if (groupBy === 'assignee') return String(task.assignee_id || '') === String(laneKey)\n  if (groupBy === 'publico') return laneKey === '__none__' ? (task.publicos || []).length === 0 : (task.publicos || []).includes(laneKey)\n  return false\n}\n\nfunction applyDropValue (groupBy, task, sourceLane, targetLane) {\n  if (groupBy === 'tema') return { field: 'tema', value: targetLane }\n  if (groupBy === 'status') return { field: 'status', value: targetLane }\n  if (groupBy === 'priority') return { field: 'priority', value: targetLane }\n  if (groupBy === 'assignee') return { field: 'assignee_id', value: targetLane || null }\n  if (groupBy === 'publico') {\n    let next = (task.publicos || []).slice()\n    if (sourceLane !== '__none__') next = next.filter(x => x !== sourceLane)\n    if (targetLane !== '__none__' && !next.includes(targetLane)) next.push(targetLane)\n    return { field: 'publicos', value: next }\n  }\n  return null\n}\n\nfunction temaMeta (tema) {\n  return TEMAS.find(t => t.tema === tema) || TEMAS[0]\n}\nfunction esc (s) {\n  return String(s == null ? '' : s)\n    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')\n}\nfunction clamp (v, lo, hi) { return Math.max(lo, Math.min(hi, v)) }\nfunction initials (name) {\n  const n = String(name || '').trim()\n  if (!n) return '?'\n  const parts = n.split(/\\s+/)\n  return (parts[0][0] + (parts[1] ? parts[1][0] : '')).toUpperCase()\n}\nfunction fmtWhen (iso) {\n  try {\n    const d = new Date(iso)\n    if (isNaN(d.getTime())) return ''\n    return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' }) + ' ' +\n      d.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })\n  } catch (e) { return '' }\n}\nfunction nowIso () { return new Date().toISOString() }\nfunction personById (people, id) {\n  if (!id) return null\n  return people.find(p => String(p._id) === String(id)) || null\n}\nfunction nextPersonColor (people) {\n  return PERSON_PALETTE[people.length % PERSON_PALETTE.length]\n}\nfunction taskTitleById (tasks, id) {\n  if (!id) return 'Geral'\n  const t = tasks.find(x => String(x._id) === String(id))\n  return t ? t.title : 'Tarefa removida'\n}\nfunction roleLabel (role) {\n  if (role === 'tech-lead') return 'Tech Lead'\n  if (role === 'interno' || role === 'squad') return 'Squad Maia'\n  if (role === 'cliente') return 'Cliente Natura'\n  return role || 'Membro'\n}\n\nfunction buildActivity (task, action, field, oldValue, newValue, by) {\n  return {\n    entity: 'task',\n    entity_id: task && task._id ? String(task._id) : '',\n    entity_title: task && task.title ? task.title : '',\n    action: action || 'update',\n    field: field || '',\n    old_value: oldValue == null ? '' : String(oldValue),\n    new_value: newValue == null ? '' : String(newValue),\n    by: by || 'Cliente',\n    at: nowIso()\n  }\n}\n\nfunction countBy (tasks, key) {\n  const out = {}\n  tasks.forEach(t => { const k = t[key] || '—'; out[k] = (out[k] || 0) + 1 })\n  return out\n}\n\n\n\nfunction renderTemaAdminModal (d2, temasList, tasks) {\n  if (!d2.state.temaAdminOpen) return ''\n  const err = d2.state.temaAdminError\n  const rows = temasList.map(t => {\n    const n = tasks.filter(x => x.tema === t.tema).length\n    const editing = d2.state.temaEditingId === t._id\n    return `\n      <div class=\"tema-row\">\n        ${editing\n          ? `<input type=\"text\" class=\"tema-rename-input\" data-id=\"${t._id}\" value=\"${esc(t.tema)}\" />\n             <button class=\"btn btn-primary btn-sm\" data-act=\"save-tema-rename\" data-id=\"${t._id}\">Salvar</button>\n             <button class=\"btn btn-ghost btn-sm\" data-act=\"cancel-tema-rename\">Cancelar</button>`\n          : `<span class=\"tema-row-name\">${esc(t.tema)}</span>\n             <span class=\"tema-row-count\">${n} tarefa${n === 1 ? '' : 's'}</span>\n             <button class=\"btn btn-ghost btn-sm\" data-act=\"start-tema-rename\" data-id=\"${t._id}\">Renomear</button>\n             <button class=\"btn btn-ghost btn-sm\" data-act=\"delete-tema\" data-id=\"${t._id}\" data-count=\"${n}\">Apagar</button>`}\n      </div>`\n  }).join('')\n  return `\n    <div class=\"overlay-scrim\" data-act=\"close-tema-admin\">\n      <div class=\"modal-panel tema-admin-panel\" data-act=\"stop\">\n        <button class=\"close-x\" data-act=\"close-tema-admin\">×</button>\n        <div class=\"modal-title\">Gerenciar temas</div>\n        ${err ? `<div class=\"login-error\">${esc(err)}</div>` : ''}\n        <div class=\"tema-admin-list\">${rows || '<div class=\"empty-hint\">Nenhum tema ainda.</div>'}</div>\n        <div class=\"tema-admin-add\">\n          <input type=\"text\" class=\"new-tema-input\" placeholder=\"Nome do novo tema\" />\n          <button class=\"btn btn-primary btn-sm\" data-act=\"add-tema\">+ Adicionar tema</button>\n        </div>\n      </div>\n    </div>`\n}\n\nfunction renderChangePasswordModal (d2) {\n  if (!d2.state.changePasswordOpen) return ''\n  const err = d2.state.cpError\n  return `\n    <div class=\"overlay-scrim\" data-act=\"close-change-password\">\n      <div class=\"modal-panel\" data-act=\"stop\">\n        <button class=\"close-x\" data-act=\"close-change-password\">×</button>\n        <div class=\"modal-title\">Alterar senha</div>\n        ${err ? `<div class=\"login-error\">${esc(err)}</div>` : ''}\n        <input type=\"password\" class=\"cp-current\" placeholder=\"Senha atual\" autocomplete=\"current-password\" />\n        <input type=\"password\" class=\"cp-new\" placeholder=\"Nova senha\" autocomplete=\"new-password\" />\n        <input type=\"password\" class=\"cp-confirm\" placeholder=\"Confirmar nova senha\" autocomplete=\"new-password\" />\n        <div class=\"modal-actions\">\n          <button class=\"btn btn-ghost\" data-act=\"close-change-password\">Cancelar</button>\n          <button class=\"btn btn-primary\" data-act=\"submit-change-password\">Salvar nova senha</button>\n        </div>\n      </div>\n    </div>`\n}\n\nfunction renderHeader (d2, tasks, currentUser) {\n  const counts = countBy(tasks, 'status')\n  const view = d2.state.view || 'canvas'\n  const chips = STATUS_ORDER.map(s => {\n    const m = STATUS_META[s]\n    const n = counts[s] || 0\n    return `<span class=\"stat-chip\" style=\"--c:${m.color}\"><b>${n}</b>${esc(m.label)}</span>`\n  }).join('')\n  return `\n    <header class=\"topbar no-pan\">\n      <div class=\"brand\">\n        <span class=\"brand-mark\">IAP</span>\n        <div class=\"brand-text\">\n          <div class=\"brand-title\">Painel de Gestão — Experimento IAP</div>\n          <div class=\"brand-sub\">Só o rastreamento deste experimento — a sustentação da Maia continua no Jira</div>\n        </div>\n      </div>\n      <div class=\"stat-row\">${chips}</div>\n      <div class=\"topbar-actions\">\n        <span class=\"who-chip\">${esc(currentUser.name)}<span class=\"who-role\">${esc(roleLabel(currentUser.role))}</span></span>\n        <button class=\"btn btn-ghost btn-sm\" data-act=\"open-tema-admin\">Gerenciar temas</button>\n        <button class=\"btn btn-ghost btn-sm\" data-act=\"open-change-password\">Alterar senha</button>\n        <button class=\"btn btn-ghost btn-sm\" data-act=\"do-logout\">Sair</button>\n        <div class=\"view-switch\" data-act=\"view-switch\">\n          <button class=\"view-btn ${view === 'canvas' ? 'is-active' : ''}\" data-view=\"canvas\">Mapa vivo</button>\n          <button class=\"view-btn ${view === 'board' ? 'is-active' : ''}\" data-view=\"board\">Quadro</button>\n          <button class=\"view-btn ${view === 'list' ? 'is-active' : ''}\" data-view=\"list\">Lista</button>\n          <button class=\"view-btn ${view === 'central' ? 'is-active' : ''}\" data-view=\"central\">Central</button>\n        </div>\n        <button class=\"btn btn-primary\" data-act=\"new-task\">+ Nova tarefa</button>\n      </div>\n    </header>`\n}\n\nfunction renderTemaNav (d2, tasks) {\n  const pills = TEMAS.map(t => {\n    const n = tasks.filter(x => x.tema === t.tema).length\n    return `<button class=\"tema-pill no-pan\" data-act=\"jump\" data-tema=\"${esc(t.tema)}\">${esc(t.tema)} <b>${n}</b></button>`\n  }).join('')\n  return `<nav class=\"tema-nav no-pan\">${pills}</nav>`\n}\n\nfunction renderCard (t, people, comments, files) {\n  const sm = STATUS_META[t.status] || STATUS_META.a_fazer\n  const pm = PRIORITY_META[t.priority] || PRIORITY_META.media\n  const assignee = personById(people, t.assignee_id)\n  const publicos = (t.publicos || []).slice(0, 3).map(p => {\n    const m = PUBLICO_META[p]\n    return m ? `<span class=\"chip-mini\" style=\"--c:${m.color}\">${esc(m.label)}</span>` : ''\n  }).join('')\n  const extra = (t.publicos || []).length > 3 ? `<span class=\"chip-mini chip-more\">+${(t.publicos.length - 3)}</span>` : ''\n  const commentCount = comments.filter(c => String(c.task_id) === String(t._id)).length\n  const pendingFiles = files.filter(f => String(f.task_id) === String(t._id) && f.status === 'pendente').length\n  const anchor = temaMeta(t.tema)\n  const idx = typeof t._clusterIdx === 'number' ? t._clusterIdx : 0\n  const left = anchor.x + 20 + (idx % 2) * 300\n  const top = anchor.y + LABEL_H + 20 + Math.floor(idx / 2) * 160\n  return `\n    <div class=\"task-card no-pan ${t._savingField ? 'is-saving' : ''}\" data-key=\"${t._id}\" data-act=\"open-task\" data-id=\"${t._id}\"\n         style=\"left:${left}px; top:${top}px; --pcolor:${pm.color}\">\n      <div class=\"card-top\">\n        <span class=\"status-dot\" style=\"--c:${sm.color}\" title=\"${esc(sm.label)}\"></span>\n        <span class=\"card-status\" style=\"--c:${sm.color}\">${esc(sm.label)}</span>\n      </div>\n      <div class=\"card-title\">${esc(t.title)}</div>\n      <div class=\"card-tags\">${publicos}${extra}</div>\n      <div class=\"card-foot\">\n        ${assignee\n          ? `<span class=\"mini-avatar\" style=\"--c:${assignee.color}\" title=\"${esc(assignee.name)}\">${esc(initials(assignee.name))}</span>`\n          : `<span class=\"mini-avatar mini-avatar-empty\">—</span>`}\n        <span class=\"foot-spacer\"></span>\n        ${commentCount ? `<span class=\"foot-badge\" title=\"dúvidas\">💬 ${commentCount}</span>` : ''}\n        ${pendingFiles ? `<span class=\"foot-badge foot-badge-warn\" title=\"arquivo pendente\">📎 ${pendingFiles}</span>` : ''}\n      </div>\n    </div>`\n}\n\nfunction renderClusters (tasks, people, comments, files) {\n  return TEMAS.map(t => {\n    const items = tasks.filter(x => x.tema === t.tema)\n    items.forEach((it, i) => { it._clusterIdx = i })\n    return `\n      <div class=\"cluster-blob\" style=\"left:${t.x}px; top:${t.y}px; width:${t.w}px; height:${t.h}px;\"></div>\n      <div class=\"cluster-label no-pan\" style=\"left:${t.x + 20}px; top:${t.y + 14}px;\">\n        ${esc(t.tema)} <span class=\"cluster-count\">${items.length}</span>\n      </div>\n      ${items.map(x => renderCard(x, people, comments, files)).join('')}\n    `\n  }).join('')\n}\n\nfunction renderCanvas (d2, tasks, people, comments, files) {\n  const zoom = d2.state.zoom || 0.82\n  const panX = d2.state.panX || 0\n  const panY = d2.state.panY || 0\n  return `\n    <div class=\"viewport\" data-act=\"viewport\">\n      <div class=\"canvas-layer\" style=\"width:${CANVAS_W}px; height:${CANVAS_H}px; transform:translate(${panX}px,${panY}px) scale(${zoom});\">\n        ${renderClusters(tasks, people, comments, files)}\n      </div>\n      <div class=\"zoom-ctl no-pan\">\n        <button class=\"zoom-btn\" data-act=\"zoom-out\">−</button>\n        <span class=\"zoom-val\">${Math.round(zoom * 100)}%</span>\n        <button class=\"zoom-btn\" data-act=\"zoom-in\">+</button>\n        <button class=\"zoom-btn zoom-fit-btn\" data-act=\"zoom-fit\" title=\"Encaixar tudo na tela\">⤢</button>\n      </div>\n      <button class=\"fab no-pan\" data-act=\"new-task\" title=\"Nova tarefa\">+</button>\n    </div>`\n}\n\nfunction renderBoardCard (t, people, comments, files, groupBy, laneKey) {\n  const sm = STATUS_META[t.status] || STATUS_META.a_fazer\n  const pm = PRIORITY_META[t.priority] || PRIORITY_META.media\n  const assignee = personById(people, t.assignee_id)\n  const publicos = (t.publicos || []).map(p => PUBLICO_META[p] ? `<span class=\"chip-mini\" style=\"--c:${PUBLICO_META[p].color}\">${esc(PUBLICO_META[p].label)}</span>` : '').join('')\n  const commentCount = comments.filter(c => String(c.task_id) === String(t._id)).length\n  const pendingFiles = files.filter(f => String(f.task_id) === String(t._id) && f.status === 'pendente').length\n  return `\n    <div class=\"board-card no-pan ${t._savingField ? 'is-saving' : ''}\" data-key=\"${t._id}__${esc(laneKey)}\" data-act=\"open-task\" data-id=\"${t._id}\"\n         data-drag-task=\"${t._id}\" data-group-by=\"${groupBy}\" data-source-lane=\"${esc(laneKey)}\"\n         style=\"--pcolor:${pm.color}\">\n      <div class=\"card-top\">\n        <span class=\"status-dot\" style=\"--c:${sm.color}\"></span>\n        <span class=\"card-status\" style=\"--c:${sm.color}\">${esc(sm.label)}</span>\n      </div>\n      <div class=\"card-title\">${esc(t.title)}</div>\n      <div class=\"card-tags\">${publicos}</div>\n      <div class=\"card-foot\">\n        ${assignee\n          ? `<span class=\"mini-avatar\" style=\"--c:${assignee.color}\" title=\"${esc(assignee.name)}\">${esc(initials(assignee.name))}</span>`\n          : `<span class=\"mini-avatar mini-avatar-empty\">—</span>`}\n        <span class=\"foot-spacer\"></span>\n        ${commentCount ? `<span class=\"foot-badge\" title=\"dúvidas\">💬 ${commentCount}</span>` : ''}\n        ${pendingFiles ? `<span class=\"foot-badge foot-badge-warn\" title=\"arquivo pendente\">📎 ${pendingFiles}</span>` : ''}\n      </div>\n    </div>`\n}\n\nfunction renderBoard (d2, tasks, people, comments, files) {\n  const groupBy = d2.state.boardGroupBy || 'status'\n  const lanes = getLanes(groupBy, people)\n  const switcher = GROUPBY_OPTIONS.map(o => `<button class=\"groupby-btn no-pan ${groupBy === o.key ? 'is-active' : ''}\" data-act=\"set-groupby\" data-value=\"${o.key}\">${esc(o.label)}</button>`).join('')\n  const prOrder = { alta: 0, media: 1, baixa: 2 }\n  const laneHtml = lanes.map(lane => {\n    const items = tasks.filter(t => taskInLane(groupBy, t, lane.key))\n      .sort((a, b) => (prOrder[a.priority] ?? 1) - (prOrder[b.priority] ?? 1) || String(a.title).localeCompare(String(b.title)))\n    return `\n      <div class=\"board-lane no-pan\" data-lane-key=\"${esc(lane.key)}\">\n        <div class=\"board-lane-header\">\n          <span class=\"board-lane-dot\" style=\"--c:${lane.color}\"></span>\n          <span class=\"board-lane-title\">${esc(lane.label)}</span>\n          <span class=\"board-lane-count\">${items.length}</span>\n        </div>\n        <div class=\"board-lane-body\">\n          ${items.map(t => renderBoardCard(t, people, comments, files, groupBy, lane.key)).join('') || '<div class=\"empty-hint board-lane-empty\">Nenhuma tarefa aqui.</div>'}\n        </div>\n      </div>`\n  }).join('')\n  return `\n    <div class=\"board-groupby-row no-pan\">${switcher}</div>\n    <div class=\"board-view no-pan\">${laneHtml}</div>`\n}\n\nfunction renderList (d2, tasks, people) {\n  const f = d2.state.listFilters || {}\n  const temaOpts = TEMAS.map(t => `<option value=\"${esc(t.tema)}\" ${f.tema === t.tema ? 'selected' : ''}>${esc(t.tema)}</option>`).join('')\n  const statusOpts = STATUS_ORDER.map(s => `<option value=\"${s}\" ${f.status === s ? 'selected' : ''}>${esc(STATUS_META[s].label)}</option>`).join('')\n  const prioOpts = PRIORITY_ORDER.map(s => `<option value=\"${s}\" ${f.priority === s ? 'selected' : ''}>${esc(PRIORITY_META[s].label)}</option>`).join('')\n  const pubOpts = PUBLICO_ORDER.map(s => `<option value=\"${s}\" ${f.publico === s ? 'selected' : ''}>${esc(PUBLICO_META[s].label)}</option>`).join('')\n  const assigneeOpts = people.map(p => `<option value=\"${p._id}\" ${f.assignee === String(p._id) ? 'selected' : ''}>${esc(p.name)}</option>`).join('')\n\n  let rows = tasks.slice()\n  if (f.tema) rows = rows.filter(t => t.tema === f.tema)\n  if (f.status) rows = rows.filter(t => t.status === f.status)\n  if (f.priority) rows = rows.filter(t => t.priority === f.priority)\n  if (f.publico) rows = rows.filter(t => (t.publicos || []).includes(f.publico))\n  if (f.assignee) rows = rows.filter(t => String(t.assignee_id) === f.assignee)\n  if (f.q && f.q.trim()) {\n    const q = f.q.trim().toLowerCase()\n    rows = rows.filter(t => String(t.title || '').toLowerCase().includes(q) || String(t.description || '').toLowerCase().includes(q))\n  }\n\n  const trs = rows.map(t => {\n    const sm = STATUS_META[t.status] || STATUS_META.a_fazer\n    const pm = PRIORITY_META[t.priority] || PRIORITY_META.media\n    const assignee = personById(people, t.assignee_id)\n    const pubs = (t.publicos || []).map(p => PUBLICO_META[p] ? `<span class=\"chip-mini\" style=\"--c:${PUBLICO_META[p].color}\">${esc(PUBLICO_META[p].label)}</span>` : '').join('')\n    return `\n      <tr class=\"list-row no-pan\" data-key=\"${t._id}\" data-act=\"open-task\" data-id=\"${t._id}\">\n        <td class=\"td-title\">${esc(t.title)}</td>\n        <td>${esc(t.tema)}</td>\n        <td><span class=\"pill-static\" style=\"--c:${sm.color}\">${esc(sm.label)}</span></td>\n        <td><span class=\"pill-static\" style=\"--c:${pm.color}\">${esc(pm.label)}</span></td>\n        <td class=\"td-tags\">${pubs}</td>\n        <td>${assignee ? esc(assignee.name) : '—'}</td>\n      </tr>`\n  }).join('')\n\n  return `\n    <div class=\"list-view no-pan\">\n      <div class=\"filters-row\">\n        <input type=\"text\" class=\"filter-search\" data-act=\"filter-search\" placeholder=\"Buscar por título ou descrição...\" value=\"${esc(f.q || '')}\" />\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"tema\"><option value=\"\">Tema — todos</option>${temaOpts}</select>\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"status\"><option value=\"\">Status — todos</option>${statusOpts}</select>\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"priority\"><option value=\"\">Prioridade — todas</option>${prioOpts}</select>\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"publico\"><option value=\"\">Público — todos</option>${pubOpts}</select>\n        <select class=\"filter-sel\" data-act=\"filter\" data-field=\"assignee\"><option value=\"\">Responsável — todos</option>${assigneeOpts}</select>\n        <button class=\"btn btn-ghost\" data-act=\"clear-filters\">Limpar filtros</button>\n        <span class=\"filter-count\">${rows.length} de ${tasks.length} tarefas</span>\n      </div>\n      <div class=\"table-wrap\">\n        <table class=\"list-table\">\n          <thead><tr><th>Título</th><th>Tema</th><th>Status</th><th>Prioridade</th><th>Públicos</th><th>Responsável</th></tr></thead>\n          <tbody>${trs || '<tr><td colspan=\"6\" class=\"td-empty\">Nenhuma tarefa encontrada com esses filtros.</td></tr>'}</tbody>\n        </table>\n      </div>\n    </div>`\n}\n\nfunction renderKnowledgeTab (d2, knowledge) {\n  const uploading = d2.state.kbUploading\n  const pending = d2.state.kbPending\n  const sorted = knowledge.slice().sort((a, b) => new Date(b.uploaded_at || 0) - new Date(a.uploaded_at || 0))\n  const list = sorted.map(k => `\n    <div class=\"hub-row kb-row\">\n      <div class=\"hub-row-main\">\n        <div class=\"hub-row-title\">${esc(k.title)}</div>\n        <div class=\"file-meta\">${esc(k.file_name || '')} · adicionado por ${esc(k.uploaded_by || '—')} · ${fmtWhen(k.uploaded_at)}</div>\n        ${k.description ? `<div class=\"kb-desc-text\">${esc(k.description)}</div>` : ''}\n      </div>\n      <a class=\"pill-static\" style=\"--c:#4C9A6B\" href=\"${esc(k.file_url || '#')}\" target=\"_blank\" rel=\"noopener\">Abrir arquivo</a>\n      <button class=\"btn btn-ghost btn-sm\" data-act=\"delete-knowledge\" data-id=\"${k._id}\" title=\"Apagar\">🗑</button>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhum documento na base de conhecimento ainda.</div>'\n\n  const dzContent = uploading\n    ? `<div class=\"kb-dz-text\">Enviando arquivo…</div>`\n    : pending\n      ? `\n        <div class=\"kb-describe\">\n          <div class=\"kb-dz-text\">Arquivo enviado: <b>${esc(pending.file_name)}</b></div>\n          <input type=\"text\" class=\"kb-title\" placeholder=\"Título (ex.: Matriz de Ação IAP v2)\" />\n          <textarea class=\"kb-desc-input\" placeholder=\"Sobre o que é esse arquivo?\"></textarea>\n          <div class=\"kb-describe-actions\">\n            <button class=\"btn btn-ghost btn-sm\" data-act=\"kb-cancel\">Cancelar</button>\n            <button class=\"btn btn-primary btn-sm\" data-act=\"kb-submit\">Adicionar à base</button>\n          </div>\n        </div>`\n      : `\n        <div class=\"kb-dz-text\">Arraste um arquivo aqui para adicionar à base de conhecimento</div>\n        <label class=\"btn btn-ghost btn-sm kb-pick-label\">ou escolher arquivo<input type=\"file\" class=\"kb-file-input\" hidden /></label>`\n\n  return `\n    <div class=\"kb-dropzone ${uploading ? 'is-busy' : ''}\" data-act=\"kb-dropzone\">${dzContent}</div>\n    <div class=\"hub-list kb-list\">${list}</div>`\n}\n\nfunction renderCentral (d2, tasks, comments, files, knowledge) {\n  const tab = d2.state.centralTab || 'files'\n  const pendingCount = files.filter(f => f.status === 'pendente').length\n  const sortedFiles = files.slice().sort((a, b) => {\n    if (a.status !== b.status) return a.status === 'pendente' ? -1 : 1\n    return new Date(b.requested_at || 0) - new Date(a.requested_at || 0)\n  })\n  const sortedComments = comments.slice().sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0))\n  const taskOpts = `<option value=\"\">Geral (não ligado a uma tarefa)</option>` + tasks.map(t => `<option value=\"${t._id}\">${esc(t.title)}</option>`).join('')\n\n  const filesHtml = sortedFiles.map(f => `\n    <div class=\"hub-row\">\n      <div class=\"hub-row-main\">\n        <div class=\"hub-row-title\">${esc(f.label)}</div>\n        <button class=\"hub-tag\" data-act=\"jump-to-task\" data-id=\"${f.task_id || ''}\">${esc(taskTitleById(tasks, f.task_id))}</button>\n        <div class=\"file-meta\">pedido por ${esc(f.requested_by || '—')} · ${fmtWhen(f.requested_at)}</div>\n      </div>\n      ${f.status === 'recebido'\n        ? `<a class=\"pill-static\" style=\"--c:#4C9A6B\" href=\"${esc(f.file_url || '#')}\" target=\"_blank\" rel=\"noopener\">${esc(f.file_name || 'Ver arquivo')}</a>`\n        : `<label class=\"upload-btn\" style=\"--c:#B98A5A\">Pendente — enviar arquivo<input type=\"file\" class=\"file-input\" data-act=\"upload-file\" data-file-id=\"${f._id}\" hidden /></label>`}\n      <button class=\"btn btn-ghost btn-sm\" data-act=\"delete-file-request\" data-id=\"${f._id}\" title=\"Apagar\">🗑</button>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhum arquivo pedido ainda.</div>'\n\n  const hubTop = sortedComments.filter(c => !c.reply_to)\n  const hubRepliesOf = (id) => sortedComments.filter(c => String(c.reply_to || '') === String(id))\n  const hubReplyForm = (parentId, taskId) => d2.state.replyOpenId === parentId\n    ? `<div class=\"reply-inline\"><input type=\"text\" class=\"reply-input\" placeholder=\"Responder…\" /><button class=\"btn btn-ghost btn-sm\" data-act=\"send-reply\" data-parent=\"${parentId}\" data-task=\"${taskId || ''}\">Enviar</button></div>`\n    : ''\n  const commentsHtml = hubTop.map(c => `\n    <div class=\"hub-row hub-row-comment\">\n      <span class=\"mini-avatar\" style=\"--c:#6E757C\">${esc(initials(c.author_name))}</span>\n      <div class=\"comment-body\">\n        <div class=\"comment-meta\"><b>${esc(c.author_name || 'Anônimo')}</b>\n          <button class=\"hub-tag\" data-act=\"jump-to-task\" data-id=\"${c.task_id || ''}\">${esc(taskTitleById(tasks, c.task_id))}</button>\n          <span>${fmtWhen(c.created_at)}</span>\n          <button class=\"btn btn-ghost btn-sm\" data-act=\"toggle-reply\" data-id=\"${c._id}\">Responder</button>\n        </div>\n        <div class=\"comment-text\">${esc(c.body)}</div>\n        ${hubReplyForm(c._id, c.task_id)}\n        ${hubRepliesOf(c._id).map(r => `\n        <div class=\"hub-row hub-row-comment comment-reply\">\n          <span class=\"mini-avatar\" style=\"--c:#6E757C\">${esc(initials(r.author_name))}</span>\n          <div class=\"comment-body\">\n            <div class=\"comment-meta\"><b>${esc(r.author_name || 'Anônimo')}</b><span>${fmtWhen(r.created_at)}</span></div>\n            <div class=\"comment-text\">${esc(r.body)}</div>\n          </div>\n        </div>`).join('')}\n      </div>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhuma dúvida registrada ainda.</div>'\n\n  return `\n    <div class=\"central-view no-pan\">\n      <div class=\"central-intro\">\n        <div class=\"central-title\">Documentos &amp; Dúvidas</div>\n        <div class=\"central-sub\">Tudo que precisa de resposta ou upload, em um só lugar — vai se acumulando como a base de conhecimento do projeto.</div>\n      </div>\n      <div class=\"central-stats\">\n        <div class=\"stat-tile\">\n          <div class=\"stat-value\">${pendingCount}</div>\n          <div class=\"stat-label\"><span class=\"stat-dot\" style=\"--c:#B98A5A\"></span>Arquivo${pendingCount === 1 ? '' : 's'} pendente${pendingCount === 1 ? '' : 's'}</div>\n        </div>\n        <div class=\"stat-tile\">\n          <div class=\"stat-value\">${hubTop.filter(c => hubRepliesOf(c._id).length === 0).length}</div>\n          <div class=\"stat-label\"><span class=\"stat-dot\" style=\"--c:#C4123F\"></span>Dúvida${hubTop.filter(c => hubRepliesOf(c._id).length === 0).length === 1 ? '' : 's'} sem resposta</div>\n        </div>\n        <div class=\"stat-tile\">\n          <div class=\"stat-value\">${knowledge.length}</div>\n          <div class=\"stat-label\"><span class=\"stat-dot\" style=\"--c:#4C9A6B\"></span>Documento${knowledge.length === 1 ? '' : 's'} na base</div>\n        </div>\n        <div class=\"stat-tile\">\n          <div class=\"stat-value\">${hubTop.length}</div>\n          <div class=\"stat-label\"><span class=\"stat-dot\" style=\"--c:#8792A2\"></span>Dúvida${hubTop.length === 1 ? '' : 's'} no total</div>\n        </div>\n      </div>\n      <div class=\"central-tabs\">\n        <button class=\"central-tab ${tab === 'files' ? 'is-active' : ''}\" data-act=\"central-tab\" data-value=\"files\">Arquivos ${pendingCount ? `<b>${pendingCount} pendente${pendingCount > 1 ? 's' : ''}</b>` : ''}</button>\n        <button class=\"central-tab ${tab === 'comments' ? 'is-active' : ''}\" data-act=\"central-tab\" data-value=\"comments\">Dúvidas</button>\n        <button class=\"central-tab ${tab === 'knowledge' ? 'is-active' : ''}\" data-act=\"central-tab\" data-value=\"knowledge\">Base de Conhecimento</button>\n      </div>\n      ${tab === 'files' ? `\n        <div class=\"inline-form central-compose\">\n          <select class=\"hub-task-sel-file\">${taskOpts}</select>\n          <input type=\"text\" class=\"hub-fr-label\" placeholder=\"O que você precisa pedir?\" />\n          <button class=\"btn btn-ghost btn-sm\" data-act=\"hub-request-file\">Pedir arquivo</button>\n        </div>\n        <div class=\"hub-list\">${filesHtml}</div>\n      ` : tab === 'comments' ? `\n        <div class=\"inline-form central-compose\">\n          <select class=\"hub-task-sel-comment\">${taskOpts}</select>\n          <input type=\"text\" class=\"hub-comment-input\" placeholder=\"Escrever uma dúvida...\" />\n          <button class=\"btn btn-primary btn-sm\" data-act=\"hub-send-comment\">Enviar</button>\n        </div>\n        <div class=\"hub-list\">${commentsHtml}</div>\n      ` : renderKnowledgeTab(d2, knowledge)}\n    </div>`\n}\n\nfunction renderDetail (d2, tasks, people, comments, files) {\n  const id = d2.state.selectedTaskId\n  if (!id) return ''\n  const t = tasks.find(x => String(x._id) === String(id))\n  if (!t) return ''\n  const sm = STATUS_META[t.status] || STATUS_META.a_fazer\n  const taskComments = comments.filter(c => String(c.task_id) === String(t._id))\n    .sort((a, b) => new Date(a.created_at || 0) - new Date(b.created_at || 0))\n  const taskFiles = files.filter(f => String(f.task_id) === String(t._id))\n\n  const statusBtns = STATUS_ORDER.map(s => {\n    const m = STATUS_META[s]\n    return `<button class=\"pill-btn ${t.status === s ? 'is-active' : ''}\" style=\"--c:${m.color}\" data-act=\"set-status\" data-value=\"${s}\">${esc(m.label)}</button>`\n  }).join('')\n  const prioBtns = PRIORITY_ORDER.map(s => {\n    const m = PRIORITY_META[s]\n    return `<button class=\"pill-btn ${t.priority === s ? 'is-active' : ''}\" style=\"--c:${m.color}\" data-act=\"set-priority\" data-value=\"${s}\">${esc(m.label)}</button>`\n  }).join('')\n  const pubChips = PUBLICO_ORDER.map(p => {\n    const m = PUBLICO_META[p]\n    const active = (t.publicos || []).includes(p)\n    return `<button class=\"chip-btn ${active ? 'is-active' : ''}\" style=\"--c:${m.color}\" data-act=\"toggle-publico\" data-value=\"${p}\">${esc(m.label)}</button>`\n  }).join('')\n  const peopleOpts = `<option value=\"\">— Sem responsável —</option>` + people.map(p =>\n    `<option value=\"${p._id}\" ${String(t.assignee_id) === String(p._id) ? 'selected' : ''}>${esc(p.name)}${p.is_client ? ' (cliente)' : ''}</option>`\n  ).join('')\n\n  const topComments = taskComments.filter(c => !c.reply_to)\n  const repliesOf = (id) => taskComments.filter(c => String(c.reply_to || '') === String(id))\n  const renderReplyForm = (parentId) => d2.state.replyOpenId === parentId\n    ? `<div class=\"reply-inline\"><input type=\"text\" class=\"reply-input\" placeholder=\"Responder…\" /><button class=\"btn btn-ghost btn-sm\" data-act=\"send-reply\" data-parent=\"${parentId}\" data-task=\"${d2.state.selectedTaskId || ''}\">Enviar</button></div>`\n    : ''\n  const commentsHtml = topComments.map(c => `\n    <div class=\"comment-row\">\n      <span class=\"mini-avatar\" style=\"--c:#6E757C\">${esc(initials(c.author_name))}</span>\n      <div class=\"comment-body\">\n        <div class=\"comment-meta\"><b>${esc(c.author_name || 'Anônimo')}</b><span>${fmtWhen(c.created_at)}</span>\n          <button class=\"btn btn-ghost btn-sm\" data-act=\"toggle-reply\" data-id=\"${c._id}\">Responder</button>\n        </div>\n        <div class=\"comment-text\">${esc(c.body)}</div>\n        ${renderReplyForm(c._id)}\n        ${repliesOf(c._id).map(r => `\n        <div class=\"comment-row comment-reply\">\n          <span class=\"mini-avatar\" style=\"--c:#6E757C\">${esc(initials(r.author_name))}</span>\n          <div class=\"comment-body\">\n            <div class=\"comment-meta\"><b>${esc(r.author_name || 'Anônimo')}</b><span>${fmtWhen(r.created_at)}</span></div>\n            <div class=\"comment-text\">${esc(r.body)}</div>\n          </div>\n        </div>`).join('')}\n      </div>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhuma dúvida registrada nesta tarefa ainda.</div>'\n\n  const filesHtml = taskFiles.map(f => `\n    <div class=\"file-row\">\n      <div class=\"file-info\">\n        <div class=\"file-label\">${esc(f.label)}</div>\n        <div class=\"file-meta\">pedido por ${esc(f.requested_by || '—')} · ${fmtWhen(f.requested_at)}</div>\n      </div>\n      ${f.status === 'recebido'\n        ? `<a class=\"pill-static\" style=\"--c:#4C9A6B\" href=\"${esc(f.file_url || '#')}\" target=\"_blank\" rel=\"noopener\">Recebido — ver arquivo</a>`\n        : `<label class=\"upload-btn\" style=\"--c:#B98A5A\">Pendente — enviar arquivo<input type=\"file\" class=\"file-input\" data-act=\"upload-file\" data-file-id=\"${f._id}\" hidden /></label>`}\n      <button class=\"btn btn-ghost btn-sm\" data-act=\"delete-file-request\" data-id=\"${f._id}\" title=\"Apagar\">🗑</button>\n    </div>`).join('') || '<div class=\"empty-hint\">Nenhum arquivo pedido para esta tarefa.</div>'\n\n  return `\n    <div class=\"overlay-scrim\" data-act=\"close-overlay\">\n      <div class=\"detail-panel\" data-act=\"stop\">\n        <button class=\"close-x\" data-act=\"close-overlay\">×</button>\n        <div class=\"detail-head\">\n          <span class=\"status-dot\" style=\"--c:${sm.color}\"></span>\n          <input class=\"title-input\" type=\"text\" value=\"${esc(t.title)}\" data-act=\"edit-title\" data-id=\"${t._id}\" />\n        </div>\n        <textarea class=\"desc-input\" placeholder=\"Descrição — o que precisa ser feito, contexto, links...\" data-act=\"edit-desc\" data-id=\"${t._id}\">${esc(t.description || '')}</textarea>\n\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Status</div>\n          <div class=\"pill-row\">${statusBtns}</div>\n        </div>\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Prioridade</div>\n          <div class=\"pill-row\">${prioBtns}</div>\n        </div>\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Públicos</div>\n          <div class=\"pill-row\">${pubChips}</div>\n        </div>\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Responsável</div>\n          <div class=\"assignee-row\">\n            <select class=\"assignee-sel\" data-act=\"set-assignee\">${peopleOpts}</select>\n            <button class=\"btn btn-ghost btn-sm\" data-act=\"open-add-person\">+ Pessoa</button>\n          </div>\n          ${d2.state.addPersonOpen ? `\n          <div class=\"add-person-inline\">\n            <input type=\"text\" class=\"ap-name\" placeholder=\"Nome da pessoa\" />\n            <label class=\"ap-client\"><input type=\"checkbox\" checked data-act=\"ap-client\" /> é do cliente</label>\n            <button class=\"btn btn-primary btn-sm\" data-act=\"save-person\">Salvar pessoa</button>\n          </div>` : ''}\n        </div>\n\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Arquivos pedidos ao cliente</div>\n          <div class=\"file-list\">${filesHtml}</div>\n          <div class=\"inline-form\">\n            <input type=\"text\" class=\"fr-label\" placeholder=\"O que você precisa pedir? (ex.: planilha X)\" />\n            <button class=\"btn btn-ghost btn-sm\" data-act=\"request-file\" data-id=\"${t._id}\">Pedir arquivo</button>\n          </div>\n        </div>\n\n        <div class=\"detail-section\">\n          <div class=\"section-label\">Dúvidas desta tarefa</div>\n          <div class=\"comment-list\">${commentsHtml}</div>\n          <div class=\"inline-form\">\n            <input type=\"text\" class=\"comment-input\" placeholder=\"Escrever uma dúvida ou observação...\" />\n            <button class=\"btn btn-primary btn-sm\" data-act=\"send-comment\" data-id=\"${t._id}\">Enviar</button>\n          </div>\n        </div>\n\n        <button class=\"btn btn-danger btn-sm delete-btn\" data-act=\"delete-task\" data-id=\"${t._id}\">Excluir tarefa</button>\n      </div>\n    </div>`\n}\n\nfunction renderNewTaskModal (d2) {\n  if (!d2.state.newTaskOpen) return ''\n  const temaOpts = TEMAS.map(t => `<option value=\"${esc(t.tema)}\">${esc(t.tema)}</option>`).join('')\n  const prioOpts = PRIORITY_ORDER.map(s => `<option value=\"${s}\" ${s === 'media' ? 'selected' : ''}>${esc(PRIORITY_META[s].label)}</option>`).join('')\n  const pubChecks = PUBLICO_ORDER.map(p => `\n    <label class=\"check-chip\"><input type=\"checkbox\" class=\"nt-publico\" value=\"${p}\" /> ${esc(PUBLICO_META[p].label)}</label>`).join('')\n  return `\n    <div class=\"overlay-scrim\" data-act=\"close-new-task\">\n      <div class=\"modal-panel\" data-act=\"stop\">\n        <button class=\"close-x\" data-act=\"close-new-task\">×</button>\n        <div class=\"modal-title\">Nova tarefa</div>\n        <input type=\"text\" class=\"nt-title\" placeholder=\"Título da tarefa\" />\n        <textarea class=\"nt-desc\" placeholder=\"Descrição (opcional)\"></textarea>\n        <div class=\"modal-row\">\n          <select class=\"nt-tema\">${temaOpts}</select>\n          <select class=\"nt-priority\">${prioOpts}</select>\n        </div>\n        <div class=\"modal-row nt-pub-row\">${pubChecks}</div>\n        <div class=\"modal-actions\">\n          <button class=\"btn btn-ghost\" data-act=\"close-new-task\">Cancelar</button>\n          <button class=\"btn btn-primary\" data-act=\"submit-new-task\">Criar tarefa</button>\n        </div>\n      </div>\n    </div>`\n}\n\nexport default {\n  css: `\n    :host { display:block; width:100%; height:100%; }\n    * { box-sizing:border-box; }\n    .app {\n      --bg:#14181C; --surface:#1E242B; --surface-2:#242B33; --border:rgba(255,255,255,0.08);\n      --text:#F2EDE7; --text-dim:#A9AFB6; --text-faint:#6E757C; --accent:#C4123F;\n      display:flex; flex-direction:column; width:100%; height:100%; background:var(--bg); color:var(--text);\n      font-family:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif; overflow:hidden; position:relative;\n    }\n    .loading-screen { display:flex; align-items:center; justify-content:center; width:100%; height:100%; background:var(--bg); color:var(--text-dim); font-size:14px; letter-spacing:0.03em; }\n\n    .login-screen { display:flex; align-items:center; justify-content:center; width:100%; height:100%; }\n    .login-card { width:360px; background:var(--surface-2); border:1px solid var(--border); border-radius:18px; padding:32px; display:flex; flex-direction:column; gap:12px; }\n    .login-brand { display:flex; justify-content:center; margin-bottom:4px; }\n    .login-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:19px; font-weight:600; text-align:center; }\n    .login-sub { font-size:12px; color:var(--text-faint); text-align:center; margin-bottom:8px; line-height:1.5; }\n    .login-error { font-size:12px; color:#E8798F; text-align:center; }\n    .login-notice { font-size:12px; color:#4C9A6B; text-align:center; }\n    .auth-tabs { display:flex; background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:999px; padding:3px; margin-bottom:4px; }\n    .auth-tab { flex:1; background:none; border:none; color:var(--text-dim); font-size:12px; padding:7px 10px; border-radius:999px; cursor:pointer; }\n    .auth-tab.is-active { background:var(--accent); color:#fff; }\n    .login-email, .login-password, .auth-email, .auth-password, .auth-password-confirm, .auth-name,\n    .cp-current, .cp-new, .cp-confirm { background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:9px; color:var(--text); font-size:13px; padding:10px 12px; width:100%; }\n    .cp-current, .cp-new, .cp-confirm { margin-bottom:10px; }\n    .login-submit { width:100%; margin-top:6px; }\n    .id-picker { width:400px; }\n    .id-card-list { display:flex; flex-direction:column; gap:8px; margin:6px 0 4px; }\n    .id-card { display:flex; align-items:center; gap:12px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:12px; padding:10px 14px; cursor:pointer; text-align:left; }\n    .id-card:hover { background:rgba(255,255,255,0.07); border-color:rgba(255,255,255,0.18); }\n    .id-card-name { flex:1; font-size:13.5px; font-weight:600; color:var(--text); }\n    .id-card-role { font-size:10.5px; color:var(--text-faint); text-transform:uppercase; letter-spacing:0.03em; }\n\n    .topbar { display:flex; align-items:center; gap:16px; height:68px; padding:0 24px; background:var(--surface-2); border-bottom:1px solid var(--border); flex-shrink:0; }\n    .brand { display:flex; align-items:center; gap:12px; }\n    .brand-mark { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-weight:700; font-size:15px; letter-spacing:0.08em; color:var(--accent); border:1px solid rgba(196,18,63,0.5); border-radius:8px; padding:4px 8px; }\n    .brand-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:17px; font-weight:600; line-height:1.15; }\n    .brand-sub { font-size:11px; color:var(--text-faint); margin-top:2px; }\n    .stat-row { display:flex; gap:10px; flex:1; justify-content:center; flex-wrap:wrap; }\n    .stat-chip { display:flex; align-items:center; gap:6px; font-size:12px; color:var(--text-dim); padding:5px 10px; border-radius:999px; background:rgba(255,255,255,0.03); border:1px solid var(--border); }\n    .stat-chip b { color:var(--c); font-size:13px; }\n    .topbar-actions { display:flex; align-items:center; gap:10px; }\n    .who-chip { font-size:12px; color:var(--text-dim); background:rgba(255,255,255,0.04); border:1px solid var(--border); padding:7px 12px; border-radius:999px; white-space:nowrap; }\n    .who-role { color:var(--text-faint); text-transform:uppercase; font-size:9.5px; margin-left:7px; letter-spacing:0.03em; }\n    .view-switch { display:flex; background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:999px; padding:3px; }\n    .view-btn { background:none; border:none; color:var(--text-dim); font-size:12px; padding:6px 14px; border-radius:999px; cursor:pointer; }\n    .view-btn.is-active { background:var(--accent); color:#fff; }\n    .btn { border-radius:9px; border:1px solid var(--border); background:rgba(255,255,255,0.04); color:var(--text); font-size:13px; padding:9px 16px; cursor:pointer; white-space:nowrap; transition:opacity .15s ease, transform .15s ease; }\n    .btn:hover { opacity:0.85; }\n    .btn:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }\n    .btn:focus:not(:focus-visible) { outline:none; }\n    .btn-primary { background:var(--accent); border-color:var(--accent); color:#fff; font-weight:600; }\n    .btn-ghost { background:transparent; }\n    .btn-danger { background:transparent; border-color:rgba(196,18,63,0.4); color:#E8798F; }\n    .btn-sm { padding:7px 12px; font-size:12px; }\n\n    .tema-nav { display:flex; gap:8px; padding:10px 24px; background:var(--surface-2); border-bottom:1px solid var(--border); overflow-x:auto; flex-shrink:0; }\n    .tema-pill { flex-shrink:0; background:rgba(255,255,255,0.04); border:1px solid var(--border); color:var(--text-dim); font-size:12px; padding:7px 12px; border-radius:999px; cursor:pointer; }\n    .tema-pill:hover { background:rgba(255,255,255,0.08); color:var(--text); }\n    .tema-pill b { color:var(--accent); margin-left:4px; }\n\n    .viewport { position:relative; flex:1; overflow:hidden; cursor:grab; background:\n        radial-gradient(circle at 1px 1px, rgba(255,255,255,0.05) 1px, transparent 0) 0 0/28px 28px, var(--bg); }\n    .viewport.is-grabbing { cursor:grabbing; }\n    .canvas-layer { position:absolute; left:0; top:0; transform-origin:0 0; }\n    .cluster-blob { position:absolute; border-radius:48px; background:radial-gradient(ellipse at 45% 35%, rgba(255,255,255,0.05), transparent 70%); pointer-events:none; }\n    .cluster-label { position:absolute; font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:13px; letter-spacing:0.03em; color:var(--text-dim); text-transform:uppercase; }\n    .cluster-count { color:var(--text-faint); font-size:11px; }\n\n    .is-saving { opacity:0.55; pointer-events:none; transition:opacity .15s ease; }\n    .task-card { position:absolute; width:216px; min-height:134px; background:var(--surface); border:1px solid var(--border); border-left:3px solid var(--pcolor); border-radius:14px; padding:12px 14px; cursor:pointer; box-shadow:0 1px 3px rgba(0,0,0,0.3); transition:transform .15s ease, box-shadow .15s ease; }\n    .task-card:hover { transform:translateY(-2px); box-shadow:0 8px 20px rgba(0,0,0,0.35); border-color:rgba(255,255,255,0.18); }\n    .card-top { display:flex; align-items:center; gap:6px; margin-bottom:8px; }\n    .status-dot { width:8px; height:8px; border-radius:50%; background:var(--c); flex-shrink:0; }\n    .card-status { font-size:10.5px; color:var(--c); text-transform:uppercase; letter-spacing:0.03em; font-weight:600; }\n    .card-title { font-size:13.5px; line-height:1.4; font-weight:600; color:var(--text); display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; }\n    .card-tags { display:flex; gap:4px; flex-wrap:wrap; margin-top:8px; }\n    .chip-mini { font-size:10px; padding:2px 7px; border-radius:999px; background:color-mix(in srgb, var(--c) 22%, transparent); color:var(--c); border:1px solid color-mix(in srgb, var(--c) 40%, transparent); }\n    .chip-more { color:var(--text-faint); background:rgba(255,255,255,0.06); border-color:var(--border); }\n    .card-foot { display:flex; align-items:center; gap:6px; margin-top:10px; }\n    .foot-spacer { flex:1; }\n    .foot-badge { font-size:10px; color:var(--text-faint); }\n    .foot-badge-warn { color:#E8A93A; }\n    .mini-avatar { width:22px; height:22px; border-radius:50%; background:var(--c); color:#fff; font-size:9.5px; font-weight:700; display:flex; align-items:center; justify-content:center; flex-shrink:0; }\n    .mini-avatar-empty { background:rgba(255,255,255,0.06); color:var(--text-faint); }\n\n    .zoom-ctl { position:absolute; right:20px; bottom:20px; display:flex; align-items:center; gap:8px; background:var(--surface-2); border:1px solid var(--border); border-radius:999px; padding:6px 10px; }\n    .zoom-btn { width:26px; height:26px; border-radius:50%; border:1px solid var(--border); background:rgba(255,255,255,0.04); color:var(--text); font-size:16px; cursor:pointer; line-height:1; }\n    .zoom-val { font-size:11px; color:var(--text-dim); width:38px; text-align:center; }\n    .fab { position:absolute; right:20px; top:20px; width:48px; height:48px; border-radius:50%; background:var(--accent); color:#fff; font-size:24px; border:none; cursor:pointer; box-shadow:0 6px 16px rgba(196,18,63,0.4); }\n\n    .list-view { flex:1; overflow:auto; padding:20px 24px; }\n    .filters-row { display:flex; gap:10px; align-items:center; margin-bottom:16px; flex-wrap:wrap; }\n    .filter-sel { background:var(--surface); border:1px solid var(--border); color:var(--text); font-size:12px; padding:8px 10px; border-radius:8px; }\n    .filter-search { background:var(--surface); border:1px solid var(--border); color:var(--text); font-size:12px; padding:8px 12px; border-radius:8px; min-width:220px; flex:1 1 220px; }\n    .filter-count { margin-left:auto; font-size:12px; color:var(--text-faint); }\n    .table-wrap { overflow-x:auto; }\n    .list-table { width:100%; border-collapse:collapse; font-size:13px; }\n    .list-table th { text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:0.04em; color:var(--text-faint); padding:8px 12px; border-bottom:1px solid var(--border); }\n    .list-row td { padding:12px; border-bottom:1px solid var(--border); cursor:pointer; }\n    .list-row:hover td { background:rgba(255,255,255,0.03); }\n    .td-title { font-weight:600; max-width:320px; }\n    .td-tags { display:flex; gap:4px; flex-wrap:wrap; }\n    .td-empty { text-align:center; color:var(--text-faint); padding:40px; }\n    .pill-static { font-size:11px; padding:4px 10px; border-radius:999px; color:var(--c); background:color-mix(in srgb, var(--c) 20%, transparent); border:1px solid color-mix(in srgb, var(--c) 40%, transparent); white-space:nowrap; }\n\n    .central-view { flex:1; overflow:auto; padding:28px 24px 40px; max-width:880px; margin:0 auto; width:100%; }\n    .central-intro { margin-bottom:20px; }\n    .central-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:21px; font-weight:600; }\n    .central-sub { font-size:12.5px; color:var(--text-faint); margin-top:6px; line-height:1.5; }\n    .central-tabs { display:flex; gap:8px; margin-bottom:18px; }\n    .central-tab { background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text-dim); font-size:13px; padding:9px 16px; border-radius:999px; cursor:pointer; }\n    .central-tab.is-active { background:var(--accent); border-color:var(--accent); color:#fff; }\n    .central-tab b { margin-left:6px; }\n    .central-compose { margin-bottom:20px; }\n    .central-compose select { background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:12px; padding:9px 10px; border-radius:8px; max-width:240px; }\n    .hub-list { display:flex; flex-direction:column; gap:10px; }\n    .hub-row { display:flex; justify-content:space-between; align-items:flex-start; gap:14px; background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:14px 16px; }\n    .hub-row-main { display:flex; flex-direction:column; gap:5px; }\n    .hub-row-title { font-size:13.5px; font-weight:600; }\n    .hub-tag { align-self:flex-start; font-size:10.5px; color:var(--text-dim); background:rgba(255,255,255,0.06); border:1px solid var(--border); padding:2px 9px; border-radius:999px; cursor:pointer; }\n\n    .overlay-scrim, .drawer-scrim { position:absolute; inset:0; background:rgba(10,12,14,0.6); backdrop-filter:blur(2px); z-index:50; display:flex; justify-content:flex-end; }\n    .overlay-scrim { align-items:stretch; }\n    .detail-panel { width:480px; max-width:92%; height:100%; background:var(--surface-2); border-left:1px solid var(--border); padding:28px; overflow-y:auto; position:relative; }\n    .tema-admin-panel { width:520px; }\n    .tema-admin-list { display:flex; flex-direction:column; gap:8px; margin:14px 0; max-height:320px; overflow-y:auto; }\n    .tema-row { display:flex; align-items:center; gap:8px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:9px; padding:8px 10px; }\n    .tema-row-name { flex:1; font-size:13px; }\n    .tema-row-count { font-size:11px; color:var(--text-dim); white-space:nowrap; }\n    .tema-rename-input { flex:1; background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:8px; color:var(--text); font-size:13px; padding:7px 10px; }\n    .tema-admin-add { display:flex; gap:8px; }\n    .new-tema-input { flex:1; background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:8px; color:var(--text); font-size:13px; padding:9px 12px; }\n    .modal-panel { margin:auto; width:460px; max-width:92%; max-height:88%; overflow-y:auto; background:var(--surface-2); border:1px solid var(--border); border-radius:18px; padding:28px; position:relative; align-self:center; }\n    .overlay-scrim:has(.modal-panel) { justify-content:center; align-items:center; }\n    .close-x { position:absolute; top:18px; right:18px; background:none; border:none; color:var(--text-faint); font-size:22px; cursor:pointer; line-height:1; }\n    .detail-head { display:flex; align-items:center; gap:10px; margin-bottom:14px; margin-top:6px; }\n    .title-input { flex:1; background:none; border:none; color:var(--text); font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:20px; font-weight:600; padding:4px 0; border-bottom:1px solid transparent; }\n    .title-input:focus { outline:none; border-bottom:1px solid var(--accent); }\n    .desc-input { width:100%; min-height:70px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:10px; color:var(--text-dim); font-size:13px; padding:10px 12px; margin-bottom:18px; resize:vertical; font-family:inherit; }\n    .detail-section { margin-bottom:20px; }\n    .section-label { font-size:11px; text-transform:uppercase; letter-spacing:0.05em; color:var(--text-faint); margin-bottom:8px; }\n    .pill-row { display:flex; gap:8px; flex-wrap:wrap; }\n    .pill-btn { font-size:12px; padding:7px 13px; border-radius:999px; border:1px solid var(--border); background:rgba(255,255,255,0.03); color:var(--text-dim); cursor:pointer; }\n    .pill-btn.is-active { background:var(--c); border-color:var(--c); color:#fff; font-weight:600; }\n    .chip-btn { font-size:12px; padding:6px 12px; border-radius:999px; border:1px solid var(--c); background:transparent; color:var(--c); cursor:pointer; }\n    .chip-btn.is-active { background:var(--c); color:#fff; }\n    .assignee-row { display:flex; gap:8px; }\n    .assignee-sel { flex:1; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:13px; padding:9px 10px; border-radius:9px; }\n    .add-person-inline { display:flex; gap:8px; margin-top:10px; align-items:center; flex-wrap:wrap; }\n    .add-person-inline .ap-name { flex:1; min-width:140px; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:12px; padding:7px 10px; border-radius:8px; }\n    .add-person-inline .claim-name { flex:1; min-width:140px; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:13px; padding:9px 10px; border-radius:8px; }\n    .id-picker .add-person-inline { justify-content:center; }\n    .ap-client { font-size:11px; color:var(--text-dim); display:flex; align-items:center; gap:4px; white-space:nowrap; }\n    .file-list, .comment-list { display:flex; flex-direction:column; gap:10px; margin-bottom:10px; }\n    .file-row { display:flex; justify-content:space-between; align-items:center; gap:10px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:10px; padding:10px 12px; }\n    .file-label { font-size:13px; font-weight:600; }\n    .file-meta { font-size:11px; color:var(--text-faint); margin-top:2px; }\n    .upload-btn { font-size:11px; padding:6px 10px; border-radius:999px; color:var(--c); border:1px dashed var(--c); cursor:pointer; white-space:nowrap; }\n    .comment-row { display:flex; gap:10px; }\n    .comment-body { flex:1; }\n    .comment-meta { display:flex; gap:8px; align-items:baseline; font-size:11px; color:var(--text-faint); flex-wrap:wrap; }\n    .comment-meta b { color:var(--text-dim); font-size:12px; }\n    .comment-text { font-size:13px; color:var(--text); margin-top:2px; line-height:1.5; }\n    .empty-hint { font-size:12px; color:var(--text-faint); font-style:italic; }\n    .inline-form { display:flex; gap:8px; }\n    .inline-form input { flex:1; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:12px; padding:9px 10px; border-radius:8px; }\n    .reply-inline { display:flex; gap:8px; margin-top:10px; padding-left:32px; }\n    .reply-input { flex:1; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:12px; padding:8px 10px; border-radius:8px; font-family:inherit; }\n    .reply-input:focus { outline:none; border-color:var(--accent); }\n    .delete-btn { margin-top:6px; width:100%; }\n\n    .modal-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:19px; font-weight:600; margin:6px 0 16px; }\n    .nt-title { width:100%; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:14px; padding:10px 12px; border-radius:9px; margin-bottom:10px; }\n    .nt-desc { width:100%; min-height:70px; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:13px; padding:10px 12px; border-radius:9px; margin-bottom:10px; font-family:inherit; resize:vertical; }\n    .modal-row { display:flex; gap:10px; margin-bottom:12px; flex-wrap:wrap; }\n    .modal-row select { flex:1; background:rgba(255,255,255,0.03); border:1px solid var(--border); color:var(--text); font-size:13px; padding:9px 10px; border-radius:9px; }\n    .nt-pub-row { gap:14px; }\n    .check-chip { font-size:12px; color:var(--text-dim); display:flex; align-items:center; gap:5px; }\n    .modal-actions { display:flex; justify-content:flex-end; gap:10px; margin-top:8px; }\n\n    .board-groupby-row { display:flex; gap:8px; padding:10px 24px; background:var(--surface-2); border-bottom:1px solid var(--border); flex-shrink:0; overflow-x:auto; }\n    .groupby-btn { flex-shrink:0; background:rgba(255,255,255,0.04); border:1px solid var(--border); color:var(--text-dim); font-size:12px; padding:7px 14px; border-radius:999px; cursor:pointer; }\n    .groupby-btn.is-active { background:var(--accent); border-color:var(--accent); color:#fff; }\n    .board-view { flex:1; overflow-x:auto; overflow-y:hidden; display:flex; gap:16px; padding:20px 24px; background:var(--bg); }\n    .board-lane { flex:0 0 268px; display:flex; flex-direction:column; background:radial-gradient(ellipse at 50% 0%, rgba(255,255,255,0.05), transparent 60%), var(--surface-2); border:1px solid var(--border); border-radius:18px; max-height:100%; transition:background .15s ease, border-color .15s ease; }\n    .board-lane.drag-over { border-color:var(--accent); background:radial-gradient(ellipse at 50% 0%, rgba(196,18,63,0.16), transparent 60%), var(--surface-2); }\n    .board-lane-header { display:flex; align-items:center; gap:8px; padding:14px 16px; border-bottom:1px solid var(--border); flex-shrink:0; }\n    .board-lane-dot { width:8px; height:8px; border-radius:50%; background:var(--c); flex-shrink:0; }\n    .board-lane-title { font-family:Georgia,'Iowan Old Style','Palatino Linotype',serif; font-size:13.5px; font-weight:600; flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }\n    .board-lane-count { font-size:11px; color:var(--text-faint); background:rgba(255,255,255,0.06); border-radius:999px; padding:2px 8px; flex-shrink:0; }\n    .board-lane-body { flex:1; overflow-y:auto; padding:12px; display:flex; flex-direction:column; gap:10px; min-height:60px; }\n    .board-lane-empty { padding:16px 4px; }\n    .board-card { position:relative; width:100%; background:var(--surface); border:1px solid var(--border); border-left:3px solid var(--pcolor); border-radius:12px; padding:12px 14px; cursor:grab; box-shadow:0 1px 3px rgba(0,0,0,0.25); }\n    .board-card:hover { border-color:rgba(255,255,255,0.18); }\n    .board-card-source { opacity:0.3; }\n    .drag-ghost { position:fixed; z-index:300; box-shadow:0 16px 32px rgba(0,0,0,0.5); transform:rotate(-1.5deg) scale(1.02); cursor:grabbing; pointer-events:none; }\n\n    .kb-dropzone { border:2px dashed var(--border); border-radius:14px; padding:28px; text-align:center; margin-bottom:20px; transition:border-color .15s ease, background .15s ease; }\n    .kb-dropzone.drag-active { border-color:var(--accent); background:rgba(196,18,63,0.08); }\n    .kb-dropzone.is-busy { border-color:var(--text-faint); }\n    .kb-dz-text { font-size:13px; color:var(--text-dim); margin-bottom:10px; }\n    .kb-pick-label { display:inline-block; cursor:pointer; }\n    .kb-describe { display:flex; flex-direction:column; gap:10px; max-width:420px; margin:0 auto; text-align:left; }\n    .kb-describe input, .kb-describe textarea { background:rgba(255,255,255,0.04); border:1px solid var(--border); border-radius:9px; color:var(--text); font-size:13px; padding:9px 12px; font-family:inherit; }\n    .kb-describe textarea { min-height:60px; resize:vertical; }\n    .kb-describe-actions { display:flex; justify-content:flex-end; gap:8px; }\n    .kb-desc-text { font-size:12px; color:var(--text-dim); margin-top:4px; line-height:1.5; }\n  `,\n\n  render (d2) {\n    const auth = d2.auth('auth')\n\n    if (!d2.state.ready) {\n      return `<div class=\"app\"><div class=\"loading-screen\">Carregando o painel de gestão do experimento IAP…</div></div>`\n    }\n\n    const accRec = d2.shared.get('iapUser', {}, { global: true }) || {}\n\n    const tasks = d2.data('tasks').records() || []\n    const people = d2.data('people').records() || []\n    const comments = d2.data('comments').records() || []\n    const files = d2.data('fileRequests').records() || []\n    const knowledge = d2.data('knowledge').records() || []\n    const temaRecords = (d2.data('iap-board-temasDataSet').records() || []).slice().sort((a, b) => (a.ordem || 0) - (b.ordem || 0))\n    TEMAS = computeTemaLayout(temaRecords, tasks)\n    const canvasSize = computeCanvasSize(TEMAS)\n    CANVAS_W = canvasSize.w\n    CANVAS_H = canvasSize.h\n\n    const uRaw0 = { email: accRec.email || '', name: accRec.name || '', role: accRec.role || '' }\n    const u = { email: uRaw0.email || (uRaw0.credentials && uRaw0.credentials.email) || '', name: uRaw0.name || (uRaw0.credentials && uRaw0.credentials.name) || '', role: uRaw0.role || (uRaw0.credentials && uRaw0.credentials.role) || '' }\n    const currentPerson = people.find(p => p.email && u.email && String(p.email).toLowerCase() === String(u.email).toLowerCase())\n\n    const view = d2.state.view || 'canvas'\n    const currentUser = { name: (currentPerson && currentPerson.name) || u.name || 'Visitante', role: u.role || 'cliente' }\n\n    return `\n      <div class=\"app\">\n        ${renderHeader(d2, tasks, currentUser)}\n        ${view === 'canvas' ? renderTemaNav(d2, tasks) : ''}\n        ${view === 'canvas' ? renderCanvas(d2, tasks, people, comments, files)\n          : view === 'board' ? renderBoard(d2, tasks, people, comments, files)\n          : view === 'list' ? renderList(d2, tasks, people)\n          : renderCentral(d2, tasks, comments, files, knowledge)}\n        ${renderDetail(d2, tasks, people, comments, files)}\n        ${renderNewTaskModal(d2)}\n        ${renderChangePasswordModal(d2)}\n        ${renderTemaAdminModal(d2, TEMAS, tasks)}\n      </div>`\n  },\n\n  mounted (d2, root) {\n    const tasksDs = d2.data('tasks')\n    const peopleDs = d2.data('people')\n    const commentsDs = d2.data('comments')\n    const filesDs = d2.data('fileRequests')\n    const knowledgeDs = d2.data('knowledge')\n    const temasDs = d2.data('iap-board-temasDataSet')\n\n    d2.setState({\n      view: 'canvas', zoom: 0.82, panX: 260, panY: 40, boardGroupBy: 'status',\n      selectedTaskId: null, newTaskOpen: false, addPersonOpen: false,\n      listFilters: {}, centralTab: 'files',\n      authMode: 'login', authError: null, authNotice: null, authBusy: false,\n      claimNewOpen: false, changePasswordOpen: false, cpError: null,\n      kbUploading: false, kbPending: null\n    })\n\n    const boot = async () => {\n      const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout após 8s')), 8000))\n      const work = (async () => {\n        await Promise.all([tasksDs.refresh(), peopleDs.refresh(), commentsDs.refresh(), filesDs.refresh(), knowledgeDs.refresh(), temasDs.refresh()])\n        await Promise.all([tasksDs.loadAll(), peopleDs.loadAll(), commentsDs.loadAll(), filesDs.loadAll(), knowledgeDs.loadAll(), temasDs.loadAll()])\n        d2.setState({ ready: true, bootError: null })\n      })()\n      try {\n        await Promise.race([work, timeout])\n      } catch (err) {\n        d2.setState({ ready: true, bootError: (err && err.message) || 'Não foi possível carregar os dados.' })\n      }\n    }\n    boot()\n\n    const currentTask = (id) => (tasksDs.records() || []).find(t => String(t._id) === String(id))\n    const who = () => {\n      const u = d2.shared.get('iapUser', {}, { global: true }) || {}\n      const p = (peopleDs.records() || []).find(x => x.email && u.email && String(x.email).toLowerCase() === String(u.email).toLowerCase())\n      return (p && p.name) || u.name || u.email || 'Cliente'\n    }\n    const refreshTasks = async () => { await tasksDs.refresh(); await tasksDs.loadAll(); d2.refresh() }\n    const refreshComments = async () => { await commentsDs.refresh(); await commentsDs.loadAll(); d2.refresh() }\n    const refreshFiles = async () => { await filesDs.refresh(); await filesDs.loadAll(); d2.refresh() }\n    const refreshPeople = async () => { await peopleDs.refresh(); await peopleDs.loadAll(); d2.refresh() }\n    const refreshKnowledge = async () => { await knowledgeDs.refresh(); await knowledgeDs.loadAll(); d2.refresh() }\n    const refreshTemas = async () => { await temasDs.refresh(); await temasDs.loadAll(); d2.refresh() }\n\n    const saveTaskField = async (id, field, value) => {\n      const t = currentTask(id)\n      if (!t) return\n      const oldValue = t[field]\n      const record = Object.assign({}, t, { [field]: value, updated_at: nowIso(), updated_by: who() })\n      const activity = buildActivity(t, 'update', field, oldValue, value, who())\n      // optimistic: patch in-memory record + mark saving, re-render immediately\n      Object.assign(t, record, { _savingField: field })\n      d2.refresh()\n      try {\n        await d2.rush('saveTask').invoke({ record, activity })\n        delete t._savingField\n        await refreshTasks()\n      } catch (err) {\n        d2.log('save task field failed', err)\n        t[field] = oldValue\n        delete t._savingField\n        d2.setState({ bootError: null })\n        d2.refresh()\n      }\n    }\n\n    // ---- auth: login / signup / logout ----\n    d2.on('[data-act=\"auth-mode\"]', 'click', (e, el) => d2.setState({ authMode: el.dataset.value, authError: null, authNotice: null }))\n    d2.on('[data-act=\"do-login\"]', 'click', async () => {\n      const email = (d2.$('.auth-email') || {}).value || ''\n      const password = (d2.$('.auth-password') || {}).value || ''\n      if (!email || !password) { d2.setState({ authError: 'Preencha e-mail e senha.' }); return }\n      d2.setState({ authBusy: true, authError: null })\n      try {\n        await d2.auth('auth').login({ credentials: { email, password } })\n        d2.setState({ authBusy: false })\n        boot()\n      } catch (err) {\n        d2.setState({ authBusy: false, authError: 'E-mail ou senha inválidos.' })\n      }\n    })\n    d2.on('[data-act=\"do-signup\"]', 'click', async () => {\n      const email = (d2.$('.auth-email') || {}).value || ''\n      const password = (d2.$('.auth-password') || {}).value || ''\n      const confirm = (d2.$('.auth-password-confirm') || {}).value || ''\n      const name = (d2.$('.auth-name') || {}).value || ''\n      const internal = (d2.$('.auth-internal') || {}).checked\n      if (!email || !password || !name) { d2.setState({ authError: 'Preencha nome, e-mail e senha.' }); return }\n      if (password !== confirm) { d2.setState({ authError: 'As senhas não coincidem.' }); return }\n      const role = email.trim().toLowerCase() === 'gui@data2.app' ? 'tech-lead' : (internal ? 'interno' : 'cliente')\n      d2.setState({ authBusy: true, authError: null })\n      try {\n        const a = d2.auth('auth')\n        d2.field(a.raw, 'credentials.email').set(email)\n        d2.field(a.raw, 'credentials.password').set(password)\n        d2.field(a.raw, 'credentials.name').set(name)\n        d2.field(a.raw, 'credentials.role').set(role)\n        d2.field(a.raw, 'registration.password').set(confirm)\n        await a.register()\n        d2.setState({ authBusy: false, authMode: 'login', authNotice: 'Conta criada! Se não entrou automaticamente, faça login com sua senha.' })\n        boot()\n      } catch (err) {\n        d2.setState({ authBusy: false, authError: 'Não foi possível criar a conta. Verifique os dados e tente novamente.' })\n      }\n    })\n    d2.on('[data-act=\"do-logout\"]', 'click', async () => {\n      await d2.auth('auth').logout()\n      window.location.reload()\n    })\n\n    // ---- claim / link a person record to the logged-in account ----\n    d2.on('[data-act=\"open-claim-new\"]', 'click', () => d2.setState({ claimNewOpen: !d2.state.claimNewOpen }))\n    d2.on('[data-act=\"claim-person\"]', 'click', async (e, el) => {\n      const authR1 = d2.auth('auth')\n      const sessR1 = authR1.session || {}\n      const uRaw1 = sessR1.user || sessR1.credentials || sessR1 || authR1.user || {}\n      const u = { email: uRaw1.email || (uRaw1.credentials && uRaw1.credentials.email) || '', name: uRaw1.name || (uRaw1.credentials && uRaw1.credentials.name) || '', role: uRaw1.role || (uRaw1.credentials && uRaw1.credentials.role) || '' }\n      const p = (peopleDs.records() || []).find(x => String(x._id) === String(el.dataset.id))\n      if (!p) return\n      const record = Object.assign({}, p, { email: u.email || '' })\n      await d2.rush('savePerson').invoke({ record })\n      await refreshPeople()\n    })\n    d2.on('[data-act=\"claim-new-submit\"]', 'click', async () => {\n      const authR1 = d2.auth('auth')\n      const sessR1 = authR1.session || {}\n      const uRaw1 = sessR1.user || sessR1.credentials || sessR1 || authR1.user || {}\n      const u = { email: uRaw1.email || (uRaw1.credentials && uRaw1.credentials.email) || '', name: uRaw1.name || (uRaw1.credentials && uRaw1.credentials.name) || '', role: uRaw1.role || (uRaw1.credentials && uRaw1.credentials.role) || '' }\n      const nameEl = d2.$('.claim-name')\n      const name = nameEl ? nameEl.value.trim() : ''\n      if (!name) return\n      const isInternal = u.role === 'interno' || u.role === 'tech-lead'\n      const record = {\n        name, color: nextPersonColor(peopleDs.records() || []),\n        is_client: !isInternal, email: u.email || '', role: isInternal ? 'squad' : 'cliente'\n      }\n      await d2.rush('savePerson').invoke({ record })\n      await refreshPeople()\n      d2.setState({ claimNewOpen: false })\n    })\n\n    // ---- gerenciar temas ----\n    d2.on('[data-act=\"open-tema-admin\"]', 'click', () => d2.setState({ temaAdminOpen: true, temaAdminError: null, temaEditingId: null }))\n    d2.on('[data-act=\"close-tema-admin\"]', 'click', (e, el) => { if (e.target === el) d2.setState({ temaAdminOpen: false, temaEditingId: null }) })\n    d2.on('[data-act=\"add-tema\"]', 'click', async () => {\n      const input = d2.$('.new-tema-input')\n      const nome = input ? input.value.trim() : ''\n      if (!nome) return\n      const existing = (temasDs.records() || [])\n      if (existing.find(t => String(t.nome).toLowerCase() === nome.toLowerCase())) {\n        d2.setState({ temaAdminError: 'Já existe um tema com esse nome.' }); return\n      }\n      const maxOrdem = existing.reduce((m, t) => Math.max(m, t.ordem || 0), 0)\n      const record = { nome, ordem: maxOrdem + 1, created_at: nowIso(), updated_at: nowIso(), updated_by: who() }\n      const act = { entity: 'tema', entity_id: '', entity_title: nome, action: 'create', by: who(), at: nowIso() }\n      await d2.rush('iap_board_save_tema').invoke({ record, activity: act })\n      if (input) input.value = ''\n      d2.setState({ temaAdminError: null })\n      await refreshTemas()\n    })\n    d2.on('[data-act=\"start-tema-rename\"]', 'click', (e, el) => d2.setState({ temaEditingId: el.dataset.id, temaAdminError: null }))\n    d2.on('[data-act=\"cancel-tema-rename\"]', 'click', () => d2.setState({ temaEditingId: null }))\n    d2.on('[data-act=\"save-tema-rename\"]', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const input = d2.$('.tema-rename-input[data-id=\"' + id + '\"]')\n      const novoNome = input ? input.value.trim() : ''\n      if (!novoNome) return\n      const t = (temasDs.records() || []).find(x => String(x._id) === String(id))\n      if (!t) return\n      const oldNome = t.nome\n      const record = Object.assign({}, t, { nome: novoNome, updated_at: nowIso(), updated_by: who() })\n      const act = { entity: 'tema', entity_id: id, entity_title: novoNome, action: 'rename', field: 'nome', old_value: oldNome, new_value: novoNome, by: who(), at: nowIso() }\n      await d2.rush('iap_board_save_tema').invoke({ record, activity: act })\n      if (oldNome !== novoNome) {\n        const affected = (tasksDs.records() || []).filter(x => x.tema === oldNome)\n        for (const task of affected) { await saveTaskField(task._id, 'tema', novoNome) }\n      }\n      d2.setState({ temaEditingId: null })\n      await refreshTemas()\n      await refreshTasks()\n    })\n    d2.on('[data-act=\"delete-tema\"]', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const count = parseInt(el.dataset.count || '0', 10)\n      if (count > 0) {\n        d2.setState({ temaAdminError: `Esse tema tem ${count} tarefa(s). Renomeie as tarefas para outro tema antes de apagar.` })\n        return\n      }\n      const ok = await d2.dialog.confirm('Apagar esse tema?')\n      if (!ok) return\n      const t = (temasDs.records() || []).find(x => String(x._id) === String(id))\n      const act = t ? { entity: 'tema', entity_id: id, entity_title: t.nome, action: 'deleted', by: who(), at: nowIso() } : null\n      await d2.rush('iap_board_delete_tema').invoke({ _id: id, activity: act })\n      await refreshTemas()\n    })\n\n    // ---- change password ----\n    d2.on('[data-act=\"open-change-password\"]', 'click', () => d2.setState({ changePasswordOpen: true, cpError: null }))\n    d2.on('[data-act=\"close-change-password\"]', 'click', (e, el) => { if (e.target === el) d2.setState({ changePasswordOpen: false }) })\n    d2.on('[data-act=\"submit-change-password\"]', 'click', async () => {\n      const cur = (d2.$('.cp-current') || {}).value || ''\n      const nw = (d2.$('.cp-new') || {}).value || ''\n      const cf = (d2.$('.cp-confirm') || {}).value || ''\n      if (!cur || !nw) { d2.setState({ cpError: 'Preencha todos os campos.' }); return }\n      if (nw !== cf) { d2.setState({ cpError: 'As senhas não coincidem.' }); return }\n      try {\n        const a = d2.auth('auth')\n        d2.field(a.raw, 'previous.password').set(cur)\n        d2.field(a.raw, 'credentials.password').set(nw)\n        d2.field(a.raw, 'registration.password').set(cf)\n        await a.changePassword()\n        d2.setState({ changePasswordOpen: false, cpError: null })\n      } catch (err) {\n        d2.setState({ cpError: 'Não foi possível alterar a senha. Confira a senha atual.' })\n      }\n    })\n\n    // ---- panning (drag empty canvas background) ----\n    let dragging = false, dragMoved = false, startX = 0, startY = 0, startPanX = 0, startPanY = 0\n    d2.on(root, 'mousedown', (e) => {\n      if ((d2.state.view || 'canvas') !== 'canvas') return\n      if (d2.state.selectedTaskId || d2.state.newTaskOpen) return\n      if (e.target.closest && e.target.closest('.no-pan')) return\n      dragging = true; dragMoved = false\n      startX = e.clientX; startY = e.clientY\n      startPanX = d2.state.panX || 0; startPanY = d2.state.panY || 0\n      const vp = d2.$('.viewport'); if (vp) vp.classList.add('is-grabbing')\n    })\n    d2.on(window, 'mousemove', (e) => {\n      if (!dragging) return\n      const dx = e.clientX - startX, dy = e.clientY - startY\n      if (Math.abs(dx) + Math.abs(dy) > 4) dragMoved = true\n      const layer = d2.$('.canvas-layer')\n      if (layer) layer.style.transform = `translate(${startPanX + dx}px, ${startPanY + dy}px) scale(${d2.state.zoom || 0.82})`\n    })\n    d2.on(window, 'mouseup', (e) => {\n      if (!dragging) return\n      dragging = false\n      const vp = d2.$('.viewport'); if (vp) vp.classList.remove('is-grabbing')\n      const dx = e.clientX - startX, dy = e.clientY - startY\n      if (dragMoved) d2.setState({ panX: startPanX + dx, panY: startPanY + dy })\n      root.__iapDragMoved = dragMoved\n    })\n\n    // ---- board: grouping switch ----\n    d2.on('[data-act=\"set-groupby\"]', 'click', (e, el) => d2.setState({ boardGroupBy: el.dataset.value }))\n\n    // ---- board: drag a card between lanes ----\n    let boardDrag = null\n    d2.on(root, 'mousedown', (e) => {\n      if (d2.state.view !== 'board') return\n      const cardEl = e.target.closest && e.target.closest('.board-card')\n      if (!cardEl) return\n      e.preventDefault()\n      const rect = cardEl.getBoundingClientRect()\n      const ghost = cardEl.cloneNode(true)\n      ghost.classList.add('drag-ghost')\n      ghost.style.position = 'fixed'\n      ghost.style.left = rect.left + 'px'\n      ghost.style.top = rect.top + 'px'\n      ghost.style.width = rect.width + 'px'\n      ghost.style.margin = '0'\n      root.appendChild(ghost)\n      cardEl.classList.add('board-card-source')\n      const laneRects = Array.prototype.slice.call(d2.$$('.board-lane')).map(el => ({ el, key: el.dataset.laneKey, rect: el.getBoundingClientRect() }))\n      boardDrag = {\n        taskId: cardEl.dataset.dragTask, groupBy: cardEl.dataset.groupBy, sourceLane: cardEl.dataset.sourceLane,\n        ghost, cardEl, laneRects, moved: false,\n        offsetX: e.clientX - rect.left, offsetY: e.clientY - rect.top\n      }\n    })\n    d2.on(window, 'mousemove', (e) => {\n      if (!boardDrag) return\n      boardDrag.moved = true\n      boardDrag.ghost.style.left = (e.clientX - boardDrag.offsetX) + 'px'\n      boardDrag.ghost.style.top = (e.clientY - boardDrag.offsetY) + 'px'\n      const hit = boardDrag.laneRects.find(l => e.clientX >= l.rect.left && e.clientX <= l.rect.right && e.clientY >= l.rect.top && e.clientY <= l.rect.bottom)\n      boardDrag.laneRects.forEach(l => l.el.classList.toggle('drag-over', !!hit && l.key === hit.key))\n    })\n    d2.on(window, 'mouseup', async (e) => {\n      if (!boardDrag) return\n      const drag = boardDrag\n      boardDrag = null\n      drag.ghost.remove()\n      drag.cardEl.classList.remove('board-card-source')\n      drag.laneRects.forEach(l => l.el.classList.remove('drag-over'))\n      root.__iapDragMoved = drag.moved\n      if (!drag.moved) return\n      const hit = drag.laneRects.find(l => e.clientX >= l.rect.left && e.clientX <= l.rect.right && e.clientY >= l.rect.top && e.clientY <= l.rect.bottom)\n      if (!hit || hit.key === drag.sourceLane) return\n      const t = currentTask(drag.taskId)\n      if (!t) return\n      const patch = applyDropValue(drag.groupBy, t, drag.sourceLane, hit.key)\n      if (patch) await saveTaskField(drag.taskId, patch.field, patch.value)\n    })\n\n    // ---- keyboard zoom ----\n    d2.on(window, 'keydown', (e) => {\n      const tag = (e.target && e.target.tagName) || ''\n      if (tag === 'INPUT' || tag === 'TEXTAREA') return\n      if ((d2.state.view || 'canvas') !== 'canvas') return\n      if (e.key === '+' || e.key === '=') d2.setState({ zoom: clamp((d2.state.zoom || 0.82) + 0.1, 0.5, 1.3) })\n      if (e.key === '-') d2.setState({ zoom: clamp((d2.state.zoom || 0.82) - 0.1, 0.5, 1.3) })\n    })\n\n    d2.on('[data-act=\"zoom-in\"]', 'click', () => d2.setState({ zoom: clamp((d2.state.zoom || 0.82) + 0.1, 0.5, 1.3) }))\n    d2.on('[data-act=\"zoom-out\"]', 'click', () => d2.setState({ zoom: clamp((d2.state.zoom || 0.82) - 0.1, 0.5, 1.3) }))\n    d2.on('[data-act=\"zoom-fit\"]', 'click', () => {\n      const fitZoom = clamp(Math.min(VIEWPORT_W / CANVAS_W, VIEWPORT_H / CANVAS_H), 0.25, 1.3)\n      const panX = (VIEWPORT_W - CANVAS_W * fitZoom) / 2\n      const panY = (VIEWPORT_H - CANVAS_H * fitZoom) / 2\n      d2.setState({ zoom: fitZoom, panX, panY })\n    })\n\n    d2.on('[data-act=\"jump\"]', 'click', (e, el) => {\n      const tema = el.dataset.tema\n      const cl = temaMeta(tema)\n      const zoom = d2.state.zoom || 0.82\n      const panX = VIEWPORT_W / 2 - (cl.x + cl.w / 2) * zoom\n      const panY = VIEWPORT_H / 2 - (cl.y + cl.h / 2) * zoom\n      d2.setState({ panX, panY })\n    })\n\n    d2.on('[data-view]', 'click', (e, el) => {\n      const v = el.dataset.view\n      d2.setState({ view: v })\n      if (v === 'central') { refreshFiles(); refreshComments(); refreshKnowledge() }\n    })\n\n    d2.on('[data-act=\"new-task\"]', 'click', () => d2.setState({ newTaskOpen: true }))\n    d2.on('[data-act=\"close-new-task\"]', 'click', (e, el) => { if (e.target === el) d2.setState({ newTaskOpen: false }) })\n    d2.on('[data-act=\"submit-new-task\"]', 'click', async (e, el) => {\n      const title = (d2.$('.nt-title') || {}).value\n      if (!title || !title.trim()) return\n      el.disabled = true\n      try {\n        const desc = (d2.$('.nt-desc') || {}).value || ''\n        const tema = (d2.$('.nt-tema') || {}).value || (TEMAS[0] && TEMAS[0].tema) || ''\n        const priority = (d2.$('.nt-priority') || {}).value || 'media'\n        const publicos = Array.prototype.slice.call(d2.$('.nt-publico')).filter(c => c.checked).map(c => c.value)\n        const record = {\n          title: title.trim(), description: desc, status: 'a_fazer', priority, publicos, tema,\n          assignee_id: null, pos_x: 0, pos_y: 0, created_at: nowIso(), updated_at: nowIso(), updated_by: who()\n        }\n        const activity = buildActivity(record, 'create', '', '', title.trim(), who())\n        await d2.rush('saveTask').invoke({ record, activity })\n        d2.setState({ newTaskOpen: false })\n        await refreshTasks()\n      } catch (err) { d2.log('create task failed', err) }\n      el.disabled = false\n    })\n\n    d2.on('[data-act=\"open-task\"]', 'click', (e, el) => {\n      if (root.__iapDragMoved) { root.__iapDragMoved = false; return }\n      d2.setState({ selectedTaskId: el.dataset.id, addPersonOpen: false })\n    })\n    d2.on('[data-act=\"close-overlay\"]', 'click', (e, el) => { if (e.target === el) d2.setState({ selectedTaskId: null, addPersonOpen: false }) })\n\n    d2.on('[data-act=\"edit-title\"]', 'blur', (e, el) => {\n      const id = el.dataset.id\n      const t = currentTask(id)\n      if (t && el.value.trim() && el.value.trim() !== t.title) saveTaskField(id, 'title', el.value.trim())\n    })\n    d2.on('[data-act=\"edit-desc\"]', 'blur', (e, el) => {\n      const id = el.dataset.id\n      const t = currentTask(id)\n      if (t && el.value !== (t.description || '')) saveTaskField(id, 'description', el.value)\n    })\n\n    d2.on('[data-act=\"set-status\"]', 'click', (e, el) => saveTaskField(d2.state.selectedTaskId, 'status', el.dataset.value))\n    d2.on('[data-act=\"set-priority\"]', 'click', (e, el) => saveTaskField(d2.state.selectedTaskId, 'priority', el.dataset.value))\n    d2.on('[data-act=\"toggle-publico\"]', 'click', (e, el) => {\n      const t = currentTask(d2.state.selectedTaskId)\n      if (!t) return\n      const cur = (t.publicos || []).slice()\n      const v = el.dataset.value\n      const next = cur.includes(v) ? cur.filter(x => x !== v) : cur.concat([v])\n      saveTaskField(d2.state.selectedTaskId, 'publicos', next)\n    })\n    d2.on('[data-act=\"set-assignee\"]', 'change', (e, el) => saveTaskField(d2.state.selectedTaskId, 'assignee_id', el.value || null))\n\n    d2.on('[data-act=\"open-add-person\"]', 'click', () => d2.setState({ addPersonOpen: !d2.state.addPersonOpen }))\n    d2.on('[data-act=\"save-person\"]', 'click', async (e, el) => {\n      const nameEl = d2.$('.ap-name')\n      const clientEl = d2.$('[data-act=\"ap-client\"]')\n      const name = nameEl ? nameEl.value.trim() : ''\n      if (!name) return\n      el.disabled = true\n      try {\n        const record = { name, color: nextPersonColor(peopleDs.records() || []), is_client: clientEl ? !!clientEl.checked : true }\n        const res = await d2.rush('savePerson').invoke({ record })\n        await refreshPeople()\n        const newId = res && res.data && res.data.id\n        if (newId) await saveTaskField(d2.state.selectedTaskId, 'assignee_id', newId)\n        d2.setState({ addPersonOpen: false })\n      } catch (err) { d2.log('save person failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"request-file\"]', 'click', async (e, el) => {\n      const input = d2.$('.fr-label')\n      const label = input ? input.value.trim() : ''\n      if (!label) return\n      el.disabled = true\n      const record = { task_id: el.dataset.id, label, status: 'pendente', requested_by: who(), requested_at: nowIso(), file_url: null, file_name: null }\n      try { await d2.rush('saveFileRequest').invoke({ record }) } catch (err) { d2.log('request file failed', err) }\n      el.disabled = false\n      if (input) input.value = ''\n      await refreshFiles()\n    })\n    d2.on('[data-act=\"upload-file\"]', 'change', async (e, el) => {\n      const file = el.files && el.files[0]\n      if (!file) return\n      const frId = el.dataset.fileId\n      try {\n        const uploaded = await d2.drive('drive').uploadFromFiles([file])\n        const up0 = uploaded && uploaded[0]\n        const fileUrl = up0 && (up0.fileUrl || up0.url || up0.publicUrl || up0.href || up0.location)\n        const fr = (filesDs.records() || []).find(f => String(f._id) === String(frId))\n        if (!fr) return\n        const record = Object.assign({}, fr, { status: 'recebido', file_url: fileUrl || null, file_name: file.name })\n        await d2.rush('saveFileRequest').invoke({ record })\n        await refreshFiles()\n      } catch (err) { d2.log('upload failed', err) }\n    })\n\n    d2.on('[data-act=\"delete-file-request\"]', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const ok = await d2.dialog.confirm('Apagar esse arquivo/solicitação?')\n      if (!ok) return\n      el.disabled = true\n      const fr = (filesDs.records() || []).find(f => String(f._id) === String(id))\n      const act = fr ? { entity: 'file_request', entity_id: id, entity_title: fr.label, action: 'deleted', by: who(), at: nowIso() } : null\n      try {\n        await d2.rush('iap_board_delete_file_request').invoke({ _id: id, activity: act })\n        await refreshFiles()\n      } catch (err) { d2.log('delete file request failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"delete-knowledge\"]', 'click', async (e, el) => {\n      const id = el.dataset.id\n      const ok = await d2.dialog.confirm('Apagar esse documento da base de conhecimento?')\n      if (!ok) return\n      el.disabled = true\n      const kb = (knowledgeDs.records() || []).find(k => String(k._id) === String(id))\n      const act = kb ? { entity: 'knowledge', entity_id: id, entity_title: kb.title, action: 'deleted', by: who(), at: nowIso() } : null\n      try {\n        await d2.rush('iap_board_delete_knowledge').invoke({ _id: id, activity: act })\n        await refreshKnowledge()\n      } catch (err) { d2.log('delete knowledge failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"toggle-reply\"]', 'click', (e, el) => {\n      const id = el.dataset.id\n      d2.setState({ replyOpenId: d2.state.replyOpenId === id ? null : id })\n    })\n\n    d2.on('[data-act=\"send-reply\"]', 'click', async (e, el) => {\n      const parentId = el.dataset.parent\n      const taskId = el.dataset.task || null\n      const input = el.parentElement ? el.parentElement.querySelector('.reply-input') : null\n      const body = input ? input.value.trim() : ''\n      if (!body) return\n      el.disabled = true\n      const record = { task_id: taskId, reply_to: parentId, author_name: who(), body, created_at: nowIso() }\n      try {\n        await d2.rush('saveComment').invoke({ record })\n        d2.setState({ replyOpenId: null })\n        await refreshComments()\n      } catch (err) { d2.log('reply failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"send-comment\"]', 'click', async (e, el) => {\n      const input = d2.$('.comment-input')\n      const body = input ? input.value.trim() : ''\n      if (!body) return\n      el.disabled = true\n      try {\n        await d2.rush('saveComment').invoke({ record: { task_id: el.dataset.id, author_id: null, author_name: who(), body, created_at: nowIso() } })\n        if (input) input.value = ''\n        await refreshComments()\n      } catch (err) { d2.log('send comment failed', err) }\n      el.disabled = false\n    })\n\n    d2.on('[data-act=\"delete-task\"]', 'click', async (e, el) => {\n      const t = currentTask(el.dataset.id)\n      if (!t) return\n      const ok = await d2.dialog.confirm(`Excluir a tarefa \"${t.title}\"? Essa ação não pode ser desfeita.`)\n      if (!ok) return\n      el.disabled = true\n      try {\n        const activity = buildActivity(t, 'delete', '', t.title, '', who())\n        await d2.rush('deleteTask').invoke({ _id: el.dataset.id, activity })\n        d2.setState({ selectedTaskId: null })\n        await refreshTasks()\n      } catch (err) { d2.log('delete task failed', err); el.disabled = false }\n    })\n\n    d2.on('[data-act=\"filter\"]', 'change', (e, el) => {\n      const f = Object.assign({}, d2.state.listFilters || {})\n      f[el.dataset.field] = el.value\n      d2.setState({ listFilters: f })\n    })\n    d2.on('[data-act=\"filter-search\"]', 'input', (e, el) => {\n      const f = Object.assign({}, d2.state.listFilters || {})\n      f.q = el.value\n      d2.setState({ listFilters: f })\n    })\n    d2.on('[data-act=\"clear-filters\"]', 'click', () => d2.setState({ listFilters: {} }))\n\n    // ---- Central hub ----\n    d2.on('[data-act=\"central-tab\"]', 'click', (e, el) => {\n      const v = el.dataset.value\n      d2.setState({ centralTab: v })\n      if (v === 'files') refreshFiles()\n      else if (v === 'comments') refreshComments()\n      else if (v === 'knowledge') refreshKnowledge()\n    })\n    d2.on('[data-act=\"jump-to-task\"]', 'click', (e, el) => {\n      const id = el.dataset.id\n      if (!id) return\n      d2.setState({ view: 'canvas', selectedTaskId: id })\n    })\n    d2.on('[data-act=\"hub-request-file\"]', 'click', async (e, el) => {\n      const taskSel = d2.$('.hub-task-sel-file')\n      const labelEl = d2.$('.hub-fr-label')\n      const label = labelEl ? labelEl.value.trim() : ''\n      if (!label) return\n      el.disabled = true\n      const record = { task_id: (taskSel && taskSel.value) || null, label, status: 'pendente', requested_by: who(), requested_at: nowIso(), file_url: null, file_name: null }\n      try { await d2.rush('saveFileRequest').invoke({ record }) } catch (err) { d2.log('hub request file failed', err) }\n      el.disabled = false\n      if (labelEl) labelEl.value = ''\n      await refreshFiles()\n    })\n    d2.on('[data-act=\"hub-send-comment\"]', 'click', async (e, el) => {\n      const taskSel = d2.$('.hub-task-sel-comment')\n      const input = d2.$('.hub-comment-input')\n      const body = input ? input.value.trim() : ''\n      if (!body) return\n      el.disabled = true\n      const record = { task_id: (taskSel && taskSel.value) || null, author_id: null, author_name: who(), body, created_at: nowIso() }\n      try {\n        await d2.rush('saveComment').invoke({ record })\n        if (input) input.value = ''\n        await refreshComments()\n      } catch (err) { d2.log('hub send comment failed', err) }\n      el.disabled = false\n    })\n\n    // ---- knowledge base drop zone ----\n    const startKbUpload = async (file) => {\n      if (!file) return\n      d2.setState({ kbUploading: true })\n      try {\n        const uploaded = await d2.drive('drive').uploadFromFiles([file])\n        const up0 = uploaded && uploaded[0]\n        const fileUrl = up0 && (up0.fileUrl || up0.url || up0.publicUrl || up0.href || up0.location)\n        d2.setState({ kbUploading: false, kbPending: { file_url: fileUrl || null, file_name: file.name } })\n      } catch (err) {\n        d2.setState({ kbUploading: false })\n        d2.log('kb upload failed', err)\n      }\n    }\n    d2.on('.kb-dropzone', 'dragover', (e, el) => { e.preventDefault(); el.classList.add('drag-active') })\n    d2.on('.kb-dropzone', 'dragleave', (e, el) => { el.classList.remove('drag-active') })\n    d2.on('.kb-dropzone', 'drop', async (e, el) => {\n      e.preventDefault()\n      el.classList.remove('drag-active')\n      const file = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0]\n      await startKbUpload(file)\n    })\n    d2.on('.kb-file-input', 'change', async (e, el) => {\n      const file = el.files && el.files[0]\n      await startKbUpload(file)\n      el.value = ''\n    })\n    d2.on('[data-act=\"kb-cancel\"]', 'click', () => d2.setState({ kbPending: null }))\n    d2.on('[data-act=\"kb-submit\"]', 'click', async () => {\n      const titleEl = d2.$('.kb-title')\n      const descEl = d2.$('.kb-desc-input')\n      const title = titleEl ? titleEl.value.trim() : ''\n      if (!title) return\n      const pending = d2.state.kbPending\n      if (!pending) return\n      const record = {\n        file_url: pending.file_url, file_name: pending.file_name,\n        title, description: descEl ? descEl.value.trim() : '',\n        tags: [], uploaded_by: who(), uploaded_at: nowIso()\n      }\n      await d2.rush('saveKnowledge').invoke({ record })\n      d2.setState({ kbPending: null })\n      await refreshKnowledge()\n    })\n  }\n}\n","at":"2026-08-10T16:30:12.606Z"}}},{"_id":"6a75f9bf0509bc4ca707fe8c","component":"data2-core-types@Component","system":"65b399e996f1fc092ed2079e","params":{"extends":"data2-core-primitives@Block","component":"kAuthGate","package":"natura-x-app","definition":{"preventEvaluation":true,"component":"data2-core-types@Definition","params":{"def":{"component":"data2-code-components@CodeHost","params":{"code":{"component":"data2-core-primitives@String","params":{"content":"export default {\n  css: `:host { display:block; width:1px; height:1px; pointer-events:none; }`,\n  render (d2) { return '' },\n  mounted (d2, root) {\n    const ds = d2.data('verifier')\n    let portalEl = null\n\n    function closePortal () {\n      if (portalEl) { portalEl.remove(); portalEl = null }\n      document.body.style.overflow = ''\n    }\n\n    function openPortal () {\n      if (portalEl) return\n      const mode0 = 'login'\n      portalEl = document.createElement('div')\n      portalEl.style.cssText = 'position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;background:#14181C;color:#F2EDE7;font-family:system-ui,-apple-system,sans-serif'\n      portalEl.innerHTML =\n        '<div class=\"d2iap-card\" style=\"background:#1E242B;border:1px solid rgba(255,255,255,0.08);border-radius:16px;padding:32px;width:360px;text-align:center\">' +\n          '<div style=\"display:inline-block;border:1px solid #C4123F;color:#C4123F;font-weight:700;font-family:Georgia,serif;padding:6px 16px;border-radius:10px;margin-bottom:16px\">IAP</div>' +\n          '<div style=\"font-size:20px;font-weight:700;margin-bottom:6px\">Painel de Gestão — Experimento IAP</div>' +\n          '<div style=\"font-size:13px;color:#A9AFB6;margin-bottom:18px\">Acesso restrito ao time do experimento e à Natura.</div>' +\n          '<div class=\"d2iap-tabs\" style=\"display:flex;gap:6px;margin-bottom:16px\">' +\n            '<button class=\"d2iap-tab\" data-v=\"login\" style=\"flex:1;padding:8px;border-radius:8px;border:1px solid #C4123F;background:rgba(196,18,63,0.15);color:#F2EDE7;cursor:pointer;font-size:13px\">Entrar</button>' +\n            '<button class=\"d2iap-tab\" data-v=\"signup\" style=\"flex:1;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:transparent;color:#A9AFB6;cursor:pointer;font-size:13px\">Criar conta</button>' +\n          '</div>' +\n          '<div class=\"d2iap-namewrap\" style=\"display:none\"><input class=\"d2iap-name\" type=\"text\" placeholder=\"Seu nome\" style=\"width:100%;margin-bottom:10px;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.04);color:#F2EDE7;font-size:14px;box-sizing:border-box\" /></div>' +\n          '<input class=\"d2iap-email\" type=\"email\" placeholder=\"E-mail\" style=\"width:100%;margin-bottom:10px;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.04);color:#F2EDE7;font-size:14px;box-sizing:border-box\" />' +\n          '<input class=\"d2iap-password\" type=\"password\" placeholder=\"Senha\" style=\"width:100%;margin-bottom:10px;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.04);color:#F2EDE7;font-size:14px;box-sizing:border-box\" />' +\n          '<button class=\"d2iap-submit\" style=\"width:100%;padding:10px;border-radius:8px;border:none;background:#C4123F;color:#fff;font-size:14px;cursor:pointer;margin-top:4px\">Entrar</button>' +\n          '<div class=\"d2iap-err\" style=\"color:#C4123F;font-size:12px;margin-top:8px;display:none\"></div>' +\n        '</div>'\n      document.body.appendChild(portalEl)\n      document.body.style.overflow = 'hidden'\n\n      let mode = mode0\n      const tabs = portalEl.querySelectorAll('.d2iap-tab')\n      const nameWrap = portalEl.querySelector('.d2iap-namewrap')\n      const submitBtn = portalEl.querySelector('.d2iap-submit')\n      const errEl = portalEl.querySelector('.d2iap-err')\n\n      function setMode (m) {\n        mode = m\n        tabs.forEach((t) => {\n          const on = t.dataset.v === m\n          t.style.background = on ? 'rgba(196,18,63,0.15)' : 'transparent'\n          t.style.borderColor = on ? '#C4123F' : 'rgba(255,255,255,0.08)'\n          t.style.color = on ? '#F2EDE7' : '#A9AFB6'\n        })\n        nameWrap.style.display = m === 'signup' ? 'block' : 'none'\n        submitBtn.textContent = m === 'signup' ? 'Criar conta' : 'Entrar'\n        errEl.style.display = 'none'\n      }\n      tabs.forEach((t) => t.addEventListener('click', () => setMode(t.dataset.v)))\n\n      submitBtn.addEventListener('click', async () => {\n        const email = portalEl.querySelector('.d2iap-email').value || ''\n        const password = portalEl.querySelector('.d2iap-password').value || ''\n        const name = portalEl.querySelector('.d2iap-name') ? portalEl.querySelector('.d2iap-name').value || '' : ''\n        if (!email || !password) { errEl.textContent = 'Preencha e-mail e senha.'; errEl.style.display = 'block'; return }\n        submitBtn.disabled = true\n        submitBtn.textContent = 'Aguarde…'\n        errEl.style.display = 'none'\n        const a = d2.auth('auth')\n        try {\n          if (mode === 'login') {\n            await a.login({ credentials: { email: email, password: password } })\n          } else {\n            d2.field(a.raw, 'credentials.email').set(email)\n            d2.field(a.raw, 'credentials.password').set(password)\n            d2.field(a.raw, 'credentials.name').set(name)\n            d2.field(a.raw, 'credentials.role').set('cliente')\n            d2.field(a.raw, 'registration.password').set(password)\n            await a.register()\n          }\n          setTimeout(() => { window.location.reload() }, 300)\n        } catch (e) {\n          submitBtn.disabled = false\n          submitBtn.textContent = mode === 'signup' ? 'Criar conta' : 'Entrar'\n          errEl.textContent = (e && e.message) || 'Falhou. Verifique os dados.'\n          errEl.style.display = 'block'\n        }\n      })\n    }\n\n    function publishUser () {\n      const rec = (ds.records() || [])[0]\n      d2.shared.set('iapUser', rec ? { email: rec.email || '', name: rec.name || '', role: rec.role || '' } : null, { global: true })\n      return rec\n    }\n\n    ds.refresh().then(() => ds.loadAll()).then(() => {\n      const rec = publishUser()\n      if (!rec) openPortal(); else closePortal()\n    }).catch(() => { publishUser(); openPortal() })\n\n    d2.onUnmount(closePortal)\n  }\n}"}},"verifier":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"verifier"}}}},"auth":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"auth"}}}}}}}},"__d2PrevCode":{"code":"export default {\n  css: `:host { display:block; width:1px; height:1px; pointer-events:none; }`,\n  render (d2) { return '' },\n  mounted (d2, root) {\n    const ds = d2.data('verifier')\n    let portalEl = null\n\n    function closePortal () {\n      if (portalEl) { portalEl.remove(); portalEl = null }\n      document.body.style.overflow = ''\n    }\n\n    function openPortal () {\n      if (portalEl) return\n      const mode0 = 'login'\n      portalEl = document.createElement('div')\n      portalEl.style.cssText = 'position:fixed;inset:0;z-index:9999;display:flex;align-items:center;justify-content:center;background:#14181C;color:#F2EDE7;font-family:system-ui,-apple-system,sans-serif'\n      portalEl.innerHTML =\n        '<div class=\"d2iap-card\" style=\"background:#1E242B;border:1px solid rgba(255,255,255,0.08);border-radius:16px;padding:32px;width:360px;text-align:center\">' +\n          '<div style=\"display:inline-block;border:1px solid #C4123F;color:#C4123F;font-weight:700;font-family:Georgia,serif;padding:6px 16px;border-radius:10px;margin-bottom:16px\">IAP</div>' +\n          '<div style=\"font-size:20px;font-weight:700;margin-bottom:6px\">Painel de Gestão — Experimento IAP</div>' +\n          '<div style=\"font-size:13px;color:#A9AFB6;margin-bottom:18px\">Acesso restrito ao time do experimento e à Natura.</div>' +\n          '<div class=\"d2iap-tabs\" style=\"display:flex;gap:6px;margin-bottom:16px\">' +\n            '<button class=\"d2iap-tab\" data-v=\"login\" style=\"flex:1;padding:8px;border-radius:8px;border:1px solid #C4123F;background:rgba(196,18,63,0.15);color:#F2EDE7;cursor:pointer;font-size:13px\">Entrar</button>' +\n            '<button class=\"d2iap-tab\" data-v=\"signup\" style=\"flex:1;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:transparent;color:#A9AFB6;cursor:pointer;font-size:13px\">Criar conta</button>' +\n          '</div>' +\n          '<div class=\"d2iap-namewrap\" style=\"display:none\"><input class=\"d2iap-name\" type=\"text\" placeholder=\"Seu nome\" style=\"width:100%;margin-bottom:10px;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.04);color:#F2EDE7;font-size:14px;box-sizing:border-box\" /></div>' +\n          '<input class=\"d2iap-email\" type=\"email\" placeholder=\"E-mail\" style=\"width:100%;margin-bottom:10px;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.04);color:#F2EDE7;font-size:14px;box-sizing:border-box\" />' +\n          '<input class=\"d2iap-password\" type=\"password\" placeholder=\"Senha\" style=\"width:100%;margin-bottom:10px;padding:10px;border-radius:8px;border:1px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.04);color:#F2EDE7;font-size:14px;box-sizing:border-box\" />' +\n          '<button class=\"d2iap-submit\" style=\"width:100%;padding:10px;border-radius:8px;border:none;background:#C4123F;color:#fff;font-size:14px;cursor:pointer;margin-top:4px\">Entrar</button>' +\n          '<div class=\"d2iap-err\" style=\"color:#C4123F;font-size:12px;margin-top:8px;display:none\"></div>' +\n        '</div>'\n      document.body.appendChild(portalEl)\n      document.body.style.overflow = 'hidden'\n\n      let mode = mode0\n      const tabs = portalEl.querySelectorAll('.d2iap-tab')\n      const nameWrap = portalEl.querySelector('.d2iap-namewrap')\n      const submitBtn = portalEl.querySelector('.d2iap-submit')\n      const errEl = portalEl.querySelector('.d2iap-err')\n\n      function setMode (m) {\n        mode = m\n        tabs.forEach((t) => {\n          const on = t.dataset.v === m\n          t.style.background = on ? 'rgba(196,18,63,0.15)' : 'transparent'\n          t.style.borderColor = on ? '#C4123F' : 'rgba(255,255,255,0.08)'\n          t.style.color = on ? '#F2EDE7' : '#A9AFB6'\n        })\n        nameWrap.style.display = m === 'signup' ? 'block' : 'none'\n        submitBtn.textContent = m === 'signup' ? 'Criar conta' : 'Entrar'\n        errEl.style.display = 'none'\n      }\n      tabs.forEach((t) => t.addEventListener('click', () => setMode(t.dataset.v)))\n\n      submitBtn.addEventListener('click', async () => {\n        const email = portalEl.querySelector('.d2iap-email').value || ''\n        const password = portalEl.querySelector('.d2iap-password').value || ''\n        const name = portalEl.querySelector('.d2iap-name') ? portalEl.querySelector('.d2iap-name').value || '' : ''\n        if (!email || !password) { errEl.textContent = 'Preencha e-mail e senha.'; errEl.style.display = 'block'; return }\n        submitBtn.disabled = true\n        submitBtn.textContent = 'Aguarde…'\n        errEl.style.display = 'none'\n        const a = d2.auth('auth')\n        try {\n          if (mode === 'login') {\n            await a.login({ credentials: { email: email, password: password } })\n          } else {\n            d2.field(a.raw, 'credentials.email').set(email)\n            d2.field(a.raw, 'credentials.password').set(password)\n            d2.field(a.raw, 'credentials.name').set(name)\n            d2.field(a.raw, 'credentials.role').set('cliente')\n            d2.field(a.raw, 'registration.password').set(password)\n            await a.register()\n          }\n          setTimeout(() => { window.location.reload() }, 300)\n        } catch (e) {\n          submitBtn.disabled = false\n          submitBtn.textContent = mode === 'signup' ? 'Criar conta' : 'Entrar'\n          errEl.textContent = (e && e.message) || 'Falhou. Verifique os dados.'\n          errEl.style.display = 'block'\n        }\n      })\n    }\n\n    ds.refresh().then(() => ds.loadAll()).then(() => {\n      const rec = (ds.records() || [])[0]\n      if (!rec) openPortal(); else closePortal()\n    }).catch(() => { openPortal() })\n\n    d2.onUnmount(closePortal)\n  }\n}","at":"2026-08-07T16:26:45.861Z"}}},{"_id":"5ed9bed7b9e0891ce913e884","component":"data2-core-types@Component","params":{"package":"data2-core","component":"DataSource","renderable":{"dimensions":{"disabled":false,"component":"data2-core-types@Dimensions","params":{"width":1366,"exactWidth":false,"exactHeight":false,"height":768}}}}},{"_id":"5f472ae463c680de4ff296e9","component":"data2-core-types@Component","params":{"package":"data2-core","component":"Data2TableDataSource","extends":"data2-core@DataSource","renderable":{"dimensions":{"disabled":false,"component":"data2-core-types@Dimensions","params":{"width":1366,"exactWidth":false,"exactHeight":false,"height":768}}}}}]}},{"reference":"ExternalComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"_id":"5f060d9b1ca96def42f3c40a","displayName":"Builder AuthStrategy","component":"data2-core@AuthenticationStrategy","system":"5e3b796d6228227ca637d0f4","params":{"credentials":[{"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"status"}}],"verifier":{"component":"data2-core-types@DataSetReference","params":{"reference":"UsersDataSet"}},"sessionShared":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},"email":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},"resetPassword":{"resetEmail":{"artboard":{"params":{"preventEvaluation":true,"reference":"nR6Q9d7qdaPtZhJ28Q8Dy"},"component":"data2-core-types@ArtboardReference"}},"resetSuccessEmail":{"artboard":{"params":{"preventEvaluation":true,"reference":"yPCJBTKo8q1Mw0B_3Kpwl"},"component":"data2-core-types@ArtboardReference"}}},"session":[{"component":"data2-core-types@DataSetColumnReference","params":{"column":"name"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},{"component":"data2-core-types@DataSetColumnReference","params":{"column":"status"}}],"sessionMetadata":{"params":{"actions":[{"params":{"compare":{"params":{"subject":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},{"params":{"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"active"}},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user.status"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is active user"}]},"component":"data2-core-logical-operators@And","displayName":"is active and has _id"}},"component":"data2-core-logical-operators@Not"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check $user"},{"params":{"action":{"params":{"pattern":{"component":"data2-core-primitives@String","params":{"content":"@data2.app"}},"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user.email"}}}}},"component":"data2-core-functions@Includes","displayName":"isData2User"},"resultName":{"component":"data2-core-primitives@String","params":{"content":"is_data2_user"}}},"component":"data2-core-flow@Step"},{"params":{"action":{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.d2_app"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"management"}}]},"component":"data2-core-functions@Includes","displayName":"isData2App Exception"},"resultName":{"component":"data2-core-primitives@String","params":{"content":"is_data2_app_exception"}}},"component":"data2-core-flow@Step"},{"params":{"resultName":{"component":"data2-core-primitives@String","params":{"content":"has_access"}},"action":{"params":{"compare":{"params":{"components":[{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"is_data2_user"}}}},{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"is_data2_app_exception"}}}}]},"component":"data2-core-logical-operators@Or"},"else":{"params":{"actions":[{"params":{"actions":[{"params":{"compare":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}},"component":"data2-core-logical-operators@Not"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check $metadata.system"},{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":"filter","args":[{"_id":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}}]},"component":"data2-core-types@Interaction"},{"params":{"compare":{"params":{"subject":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records.length"}}}}},"component":"data2-core-logical-operators@Not","displayName":"systems not found"},"then":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"}},"component":"data2-core-flow@If","displayName":"check system found"},{"params":{"actions":[{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":"filter","args":[{"user":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},"organization":{"component":"data2-core-types@Getter","params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"cursor.current.organization._id"}}}}}]},"component":"data2-core-types@Interaction"},{"params":{"module":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":"filter","args":[{"user":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$user._id"}}}},"system":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}}]},"component":"data2-core-types@Interaction"}]},"component":"data2-core-flow@Parallel"}]},"component":"data2-core-flow@Series","displayName":"common setup"},{"params":{"then":{"params":{"content":true},"component":"data2-core-primitives@Boolean"},"else":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"compare":{"params":{"components":{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"WuUa-VKHhW9uYU0E3-mJ8"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"mapper":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"system"}}],"body":{"params":{"expression":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.visibility"}}}},"defaultCase":{"params":{"content":false},"component":"data2-core-primitives@Boolean"},"cases":[{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"public"}},"value":{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"owner"}},{"component":"data2-core-primitives@String","params":{"content":"billing-owner"}},{"component":"data2-core-primitives@String","params":{"content":"admin"}},{"component":"data2-core-primitives@String","params":{"content":"member"}}]},"component":"data2-core-functions@Includes","displayName":"has role with access"}]},"component":"data2-core-logical-operators@And","displayName":"has org member role with access"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is org member"},{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"comparisonSubject":{"component":"data2-core-primitives@String","params":{"content":"collaborator"}}},"component":"data2-core-comparison-operators@Equal","displayName":"is collaborator"}]},"component":"data2-core-logical-operators@And","displayName":"is org member and collaborator"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is collaborator"},{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"access"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"access.system._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"has system access"}]},"component":"data2-core-logical-operators@And","displayName":"is collaborator and has system access"}]},"component":"data2-core-logical-operators@Or"}}},{"component":"data2-core-flow@Case","params":{"expression":{"component":"data2-core-primitives@String","params":{"content":"private"}},"value":{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"components":[{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal","displayName":"is org member"},{"params":{"pattern":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.role"}}}},"subject":[{"component":"data2-core-primitives@String","params":{"content":"owner"}},{"component":"data2-core-primitives@String","params":{"content":"admin"}}]},"component":"data2-core-functions@Includes","displayName":"is owner/admin"}]},"component":"data2-core-logical-operators@And","displayName":"has org member role with access"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is owner/admin"},{"params":{"components":[{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"Ok6Tsu5XkS7r8PyW_7TyW"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"member"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"member.organization._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system.organization._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"is org member"},{"params":{"array":{"params":{"module":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"NqKAY34n2ffFL64zamp7t"}},"prop":{"component":"data2-core-primitives@String","params":{"content":"data.records"}}},"component":"data2-core-types@Getter"},"filter":{"disabled":false,"component":"data2-core-functions@Function","params":{"parameters":[{"component":"data2-core-primitives@String","params":{"content":"access"}}],"body":{"params":{"subject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"access.system._id"}}}},"comparisonSubject":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"system._id"}}}}},"component":"data2-core-comparison-operators@Equal"}}}},"component":"data2-core-functions@ArrayFind","displayName":"has system access"}]},"component":"data2-core-logical-operators@And","displayName":"is member and has system access"}]},"component":"data2-core-logical-operators@Or","displayName":"is owner/admin or has system access"}}}]},"component":"data2-core-flow@Switch"}}}},"component":"data2-core-functions@ArrayMap"}},"component":"data2-core-logical-operators@And","displayName":"has org member for all systems in request"}},"component":"data2-core-flow@If"}]},"component":"data2-core-flow@Series"},"then":{"params":{"content":true},"component":"data2-core-primitives@Boolean"}},"component":"data2-core-flow@If"}},"component":"data2-core-flow@Step"},{"params":{"then":{"system":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"$metadata.system"}}}}},"else":{"params":{"message":{"component":"data2-core-primitives@String","params":{"content":"Invalid Credentials"}},"code":{"component":"data2-core-primitives@String","params":{"content":"401"}}},"component":"data2-core-types@Error"},"compare":{"component":"data2-core-types@Reference","params":{"reference":{"component":"data2-core-primitives@String","params":{"content":"has_access"}}}}},"component":"data2-core-flow@If"}]},"component":"data2-core-flow@Series"},"sessionExpiresIn":{"component":"data2-core-primitives@Number","params":{"content":604800}},"sessionRenewal":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"identityProviders":[{"provider":{"component":"data2-core-primitives@String","params":{"content":"GOOGLE"}},"clientId":{"component":"data2-core-primitives@String","params":{"content":"55470861603-22pvfc1t49lp8cjnbfk71s5ct9hp1q1v.apps.googleusercontent.com"}}}]},"reference":"Data2BuilderAuthentication","updatedAt":"2026-08-06T19:31:23.557Z"},{"_id":"5f060ee51ca96def42f3fbd1","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"UsersTableDataSource"}},"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":1}}}},"reference":"UsersDataSet","updatedAt":"2026-03-04T21:16:40.291Z"},{"_id":"68f938cf5fb002e33752a43d","displayName":"InternalApplicationsAuthenticationStrategy","component":"data2-core@AuthenticationStrategy","system":"5e3b796d6228227ca637d0f4","params":{"resetPassword":{"resetEmail":{"artboard":{"params":{"preventEvaluation":true},"component":"data2-core-types@ArtboardReference"}},"resetSuccessEmail":{"artboard":{"params":{"preventEvaluation":true},"component":"data2-core-types@ArtboardReference"}}},"verifier":{"disabled":false,"component":"data2-core-types@DataSetReference","params":{"reference":"InternalApplicationsVerifierDataSet"}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"secret"}},"credentials":[{"component":"data2-core-types@FieldReference","params":{"key":"application"}},{"component":"data2-core-types@FieldReference","params":{"key":"secret"}},{"component":"data2-core-types@FieldReference","params":{"key":"status"}}],"session":[{"component":"data2-core-types@FieldReference","params":{"key":"application"}},{"component":"data2-core-types@FieldReference","params":{"key":"secret"}},{"component":"data2-core-types@FieldReference","params":{"key":"status"}}]},"reference":"InternalApplicationsAuthenticationStrategy","updatedAt":"2025-10-22T20:08:54.442Z","createdAt":"2025-10-22T20:04:31.198Z"},{"component":"data2-core@DataSet","reference":"SessionsDataSet","params":{"requiresConfirmation":false,"pk":"key","datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"data2sessionsdatasource"}}}},{"reference":"UsersTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}},{"component":"data2-core@ServerSessionsDataSource","reference":"data2sessionsdatasource","params":{}},{"_id":"5f16172a0528b92601890f85","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":{"component":"data2-core-primitives@Number","params":{"content":10000}}},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"DataComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"DataComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:12.831Z","createdAt":"2020-07-20T22:14:02.097Z"},{"reference":"DataComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a75d77c91fcc12ded02ba4e","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"iap-board-accounts"}}},"reference":"iap-board-accountsDataSet"},{"_id":"6a75d79e91fcc12ded02ba9d","component":"data2-core@AuthenticationStrategy","system":"65b399e996f1fc092ed2079e","params":{"credentials":[{"component":"data2-core-types@FieldReference","params":{"key":"email"},"break":false},{"component":"data2-core-types@FieldReference","params":{"key":"password"},"break":false}],"verifier":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap-board-accountsDataSet"}},"password":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"password"}},"email":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"email"}},"role":{"disabled":false,"component":"data2-core-types@DataSetColumnReference","params":{"column":"role"}}},"reference":"iapAccountAuth"},{"_id":"6a75d3b8926425ffe029dbc1","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"iap-board-knowledge"}}},"reference":"iap-board-knowledgeDataSet"},{"_id":"6a75bcdf91fcc12ded029568","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"iap-board-file-requests"}}},"reference":"iap-board-file-requestsDataSet"},{"_id":"6a75bcd89f1ece2343b51a66","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"iap-board-comments"}}},"reference":"iap-board-commentsDataSet"},{"_id":"6a75bc8c1b86a79670aa97a6","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"iap-board-people"}}},"reference":"iap-board-peopleDataSet"},{"_id":"6a75bcaf464ed6666136888f","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"iap-board-tasks"}}},"reference":"iap-board-tasksDataSet"},{"_id":"6a75d77c91fcc12ded02ba49","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"iap-board-accounts","schema":[{"component":"data2-core-types@Field","params":{"key":"email","title":"E-mail","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"password","title":"Senha","component":"data2-core-types@Password","required":true}},{"component":"data2-core-types@Field","params":{"key":"name","title":"Nome","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"role","title":"Papel","component":"data2-core-primitives@String"}}]},"reference":"iap-board-accounts"},{"_id":"6a75d3b9b56e0bf3babcdce4","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"d2-rush-http-logs--iap_board_save_knowledge"}},"pagination":{"size":20},"sort":{"_id":-1}},"reference":"iap_board_save_knowledgeLogReadDataSet","type":"logs"},{"_id":"6a75d3b91b86a79670aab994","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"d2-rush-http-logs--iap_board_save_knowledge"}},"pagination":{"size":0}},"reference":"iap_board_save_knowledgeLogInsertDataSet","type":"logs"},{"_id":"6a75d3b89f1ece2343b53c16","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"iap-board-knowledge","schema":[{"component":"data2-core-types@Field","params":{"key":"file_url","title":"URL do arquivo","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"file_name","title":"Nome do arquivo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"title","title":"Título","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"description","title":"Descrição","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"tags","title":"Tags","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"uploaded_by","title":"Adicionado por","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"uploaded_at","title":"Adicionado em","component":"data2-core-primitives@String"}}]},"reference":"iap-board-knowledge"},{"_id":"6a75bd130a2ee608f26676c5","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"d2-rush-http-logs--iap_board_save_person"}},"pagination":{"size":20},"sort":{"_id":-1}},"reference":"iap_board_save_personLogReadDataSet","type":"logs"},{"_id":"6a75bd13e45e25fa9e454afd","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"d2-rush-http-logs--iap_board_save_person"}},"pagination":{"size":0}},"reference":"iap_board_save_personLogInsertDataSet","type":"logs"},{"_id":"6a75bd119f1ece2343b51abd","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"d2-rush-http-logs--iap_board_save_file_request"}},"pagination":{"size":20},"sort":{"_id":-1}},"reference":"iap_board_save_file_requestLogReadDataSet","type":"logs"},{"_id":"6a75bd11f183062aef2765e0","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"d2-rush-http-logs--iap_board_save_file_request"}},"pagination":{"size":0}},"reference":"iap_board_save_file_requestLogInsertDataSet","type":"logs"},{"_id":"6a75bd0e1b86a79670aa9824","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"d2-rush-http-logs--iap_board_save_comment"}},"pagination":{"size":20},"sort":{"_id":-1}},"reference":"iap_board_save_commentLogReadDataSet","type":"logs"},{"_id":"6a75bd0ef183062aef2765d6","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"d2-rush-http-logs--iap_board_save_comment"}},"pagination":{"size":0}},"reference":"iap_board_save_commentLogInsertDataSet","type":"logs"},{"_id":"6a75bcf9e8875522e2d9d663","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"d2-rush-http-logs--iap_board_delete_task"}},"pagination":{"size":20},"sort":{"_id":-1}},"reference":"iap_board_delete_taskLogReadDataSet","type":"logs"},{"_id":"6a75bcf99bd28d3c6863001f","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"d2-rush-http-logs--iap_board_delete_task"}},"pagination":{"size":0}},"reference":"iap_board_delete_taskLogInsertDataSet","type":"logs"},{"_id":"6a75bd0c91fcc12ded0295a0","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"d2-rush-http-logs--iap_board_save_task"}},"pagination":{"size":20},"sort":{"_id":-1}},"reference":"iap_board_save_taskLogReadDataSet","type":"logs"},{"_id":"6a75bd0ce8875522e2d9d66d","component":"data2-core@DataSet","system":"65b399e996f1fc092ed2079e","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"d2-rush-http-logs--iap_board_save_task"}},"pagination":{"size":0}},"reference":"iap_board_save_taskLogInsertDataSet","type":"logs"},{"_id":"6a75bcdfe45e25fa9e454ac0","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"iap-board-file-requests","schema":[{"component":"data2-core-types@Field","params":{"key":"task_id","title":"ID da tarefa","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"label","title":"O que foi pedido","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"requested_by","title":"Pedido por","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"requested_at","title":"Pedido em","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"file_url","title":"URL do arquivo","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"file_name","title":"Nome do arquivo","component":"data2-core-primitives@String"}}]},"reference":"iap-board-file-requests"},{"_id":"6a75bcd8464ed666613688ad","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"iap-board-comments","schema":[{"component":"data2-core-types@Field","params":{"key":"task_id","title":"ID da tarefa","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"author_id","title":"ID do autor","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"author_name","title":"Nome do autor","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"body","title":"Mensagem","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"created_at","title":"Criado em","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"reply_to","title":"Resposta a","component":"data2-core-primitives@String"}}]},"reference":"iap-board-comments"},{"_id":"6a75bc8ce8875522e2d9d5f3","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"iap-board-people","schema":[{"component":"data2-core-types@Field","params":{"key":"name","title":"Nome","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"color","title":"Cor","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"is_client","title":"É do cliente","component":"data2-core-primitives@Boolean","required":true}},{"component":"data2-core-types@Field","params":{"key":"email","title":"E-mail","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"password","title":"Senha","component":"data2-core-types@Password"}},{"component":"data2-core-types@Field","params":{"key":"role","title":"Papel","component":"data2-core-primitives@String"}}]},"reference":"iap-board-people"},{"_id":"6a75bcaf1b86a79670aa97cb","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":"iap-board-tasks","schema":[{"component":"data2-core-types@Field","params":{"key":"title","title":"Título","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"description","title":"Descrição","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"status","title":"Status","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"priority","title":"Prioridade","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"publicos","title":"Públicos","component":"data2-core-primitives@Array"}},{"component":"data2-core-types@Field","params":{"key":"tema","title":"Tema","component":"data2-core-primitives@String","required":true}},{"component":"data2-core-types@Field","params":{"key":"assignee_id","title":"Responsável","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"pos_x","title":"Posição X","component":"data2-core-primitives@Number","required":true}},{"component":"data2-core-types@Field","params":{"key":"pos_y","title":"Posição Y","component":"data2-core-primitives@Number","required":true}},{"component":"data2-core-types@Field","params":{"key":"created_at","title":"Criado em","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"updated_at","title":"Atualizado em","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"updated_by","title":"Atualizado por","component":"data2-core-primitives@String"}}]},"reference":"iap-board-tasks"},{"_id":"6a75d3b9926425ffe029dbc6","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":{"component":"data2-core-primitives@String","params":{"content":"d2-rush-http-logs--iap_board_save_knowledge"}},"multi":["create"],"useEstimatedDocumentCount":true},"reference":"d2-rush-http-logs--iap_board_save_knowledge","type":"logs"},{"_id":"6a75bd13db9ccb59918560d5","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":{"component":"data2-core-primitives@String","params":{"content":"d2-rush-http-logs--iap_board_save_person"}},"multi":["create"],"useEstimatedDocumentCount":true},"reference":"d2-rush-http-logs--iap_board_save_person","type":"logs"},{"_id":"6a75bd118bf663d9684a7d68","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":{"component":"data2-core-primitives@String","params":{"content":"d2-rush-http-logs--iap_board_save_file_request"}},"multi":["create"],"useEstimatedDocumentCount":true},"reference":"d2-rush-http-logs--iap_board_save_file_request","type":"logs"},{"_id":"6a75bd0ee45e25fa9e454af8","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":{"component":"data2-core-primitives@String","params":{"content":"d2-rush-http-logs--iap_board_save_comment"}},"multi":["create"],"useEstimatedDocumentCount":true},"reference":"d2-rush-http-logs--iap_board_save_comment","type":"logs"},{"_id":"6a75bcf99f1ece2343b51a9d","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":{"component":"data2-core-primitives@String","params":{"content":"d2-rush-http-logs--iap_board_delete_task"}},"multi":["create"],"useEstimatedDocumentCount":true},"reference":"d2-rush-http-logs--iap_board_delete_task","type":"logs"},{"_id":"6a75bd0cf183062aef2765d1","component":"data2-core@Data2TableDataSource","system":"65b399e996f1fc092ed2079e","params":{"table":{"component":"data2-core-primitives@String","params":{"content":"d2-rush-http-logs--iap_board_save_task"}},"multi":["create"],"useEstimatedDocumentCount":true},"reference":"d2-rush-http-logs--iap_board_save_task","type":"logs"}]}},{"_id":"5f1600b70528b92601890f75","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ServiceComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ServiceComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:20.208Z","createdAt":"2020-07-20T20:38:15.436Z"},{"_id":"5f1602190528b92601890f7d","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"pagination":{"size":40000},"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ComponentInstancesTableDataSource"}},"sort":{"_id":1},"pk":"_id","autoEdit":true,"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}},"policies":{"params":{"allowGet":{"component":"data2-core-primitives@Boolean","params":{"content":true}}},"component":"data2@hkXy-UhMjhyfptdE6_fpW"}},"reference":"ComponentInstancesDataSet","updatedAt":"2025-11-06T21:07:07.089Z","createdAt":"2020-07-20T20:44:09.350Z"},{"_id":"5f16017f0528b92601890f7a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"FileComponentInstancesDataSet","updatedAt":"2023-08-03T16:08:18.193Z","createdAt":"2020-07-20T20:41:35.797Z"},{"_id":"5f1601990528b92601890f7b","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"ApplicationsTableDataSource"}},"pk":"_id","autoEdit":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"master":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"params":{"column":"_id"},"component":"data2-core-types@DataSetColumnReference"}},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"ApplicationsDataSet","updatedAt":"2022-12-13T21:51:13.995Z","createdAt":"2020-07-20T20:42:01.657Z"},{"_id":"60ddc1bbda3861ca65407c5a","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"domain":{"component":"data2-core-types@Reference","params":{"reference":"clientApplicationDomain"}}},"pagination":{"size":1},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"}},"reference":"OneFileComponentInstancesDataSet","updatedAt":"2022-02-09T19:44:38.036Z","createdAt":"2020-07-20T20:41:35.797Z"},{"_id":"63ee3d38b8c9838c62fb05fb","displayName":"DriveFileComponentInstancesDataSet","component":"data2-core@DataSet","system":"5e3b796d6228227ca637d0f4","params":{"datasource":{"component":"data2-core-types@DataSourceReference","params":{"reference":"FileComponentInstancesTableDataSource"}},"pk":"_id","autoEdit":true,"filter":{"component":{"component":"data2-core-primitives@String","params":{"content":"data2-core@Drive"}}},"master":{"params":{"dataset":{"component":"data2-core-types@DataSetReference","params":{"reference":"SystemsDataSet"}},"localField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"system"}},"masterField":{"component":"data2-core-types@DataSetColumnReference","params":{"column":"_id"}}},"component":"data2-core-types@DataSetMaster"},"autoSelect":{"component":"data2-core-primitives@Boolean","params":{"content":false}}},"reference":"DriveFileComponentInstancesDataSet","updatedAt":"2025-12-04T21:58:32.367Z","createdAt":"2023-02-16T14:27:07.797Z"},{"reference":"ServiceComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a75d3c09bd28d3c68631eb5","component":"data2-rush@HTTPListener","reference":"iap_board_save_knowledge","system":"65b399e996f1fc092ed2079e","params":{"enableExecutionLogs":true,"insertLogsDataSet":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap_board_save_knowledgeLogInsertDataSet"}},"readLogsDataSet":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap_board_save_knowledgeLogReadDataSet"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapAccountAuth"}}}}]}},{"_id":"6a75bd15b56e0bf3babcbd42","component":"data2-rush@HTTPListener","reference":"iap_board_save_person","system":"65b399e996f1fc092ed2079e","params":{"enableExecutionLogs":true,"insertLogsDataSet":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap_board_save_personLogInsertDataSet"}},"readLogsDataSet":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap_board_save_personLogReadDataSet"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapAccountAuth"}}}}]}},{"_id":"6a75bd138bf663d9684a7d6d","component":"data2-rush@HTTPListener","reference":"iap_board_save_file_request","system":"65b399e996f1fc092ed2079e","params":{"enableExecutionLogs":true,"insertLogsDataSet":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap_board_save_file_requestLogInsertDataSet"}},"readLogsDataSet":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap_board_save_file_requestLogReadDataSet"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapAccountAuth"}}}}]}},{"_id":"6a75bd10f183062aef2765db","component":"data2-rush@HTTPListener","reference":"iap_board_save_comment","system":"65b399e996f1fc092ed2079e","params":{"enableExecutionLogs":true,"insertLogsDataSet":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap_board_save_commentLogInsertDataSet"}},"readLogsDataSet":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap_board_save_commentLogReadDataSet"}},"policies":[{"component":"data2-core-auth@PublicPolicy","params":{"reason":"chamado pelo iap_mcp (endpoint externo protegido por bearer token no handler)"}}]}},{"_id":"6a75bcfb91fcc12ded02958c","component":"data2-rush@HTTPListener","reference":"iap_board_delete_task","system":"65b399e996f1fc092ed2079e","params":{"enableExecutionLogs":true,"insertLogsDataSet":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap_board_delete_taskLogInsertDataSet"}},"readLogsDataSet":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap_board_delete_taskLogReadDataSet"}},"policies":[{"component":"data2-core-auth@Policy","params":{"authenticationStrategy":{"disabled":false,"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapAccountAuth"}}}}]}},{"_id":"6a75bd0de8875522e2d9d674","component":"data2-rush@HTTPListener","reference":"iap_board_save_task","system":"65b399e996f1fc092ed2079e","params":{"enableExecutionLogs":true,"insertLogsDataSet":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap_board_save_taskLogInsertDataSet"}},"readLogsDataSet":{"component":"data2-core-types@DataSetReference","params":{"reference":"iap_board_save_taskLogReadDataSet"}},"policies":[{"component":"data2-core-auth@PublicPolicy","params":{"reason":"chamado pelo iap_mcp (endpoint externo protegido por bearer token no handler)"}}]}}]}},{"reference":"ComponentInstancesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a75c0cf8bf663d9684a81d2","component":"data2-core-primitives@Artboard","system":"65b399e996f1fc092ed2079e","displayName":"IAP Board","params":{"title":{"component":"data2-core-primitives@String","params":{"content":"IAP Board"}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1600,"height":1000}},"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0}}},"reference":"jdjrJRm81oW3RyzM7flgj"},{"_id":"d5ac335bdbaecaba7ddd73bc","parent":"6a75c0cf8bf663d9684a81d2","component":"natura-x-app@kIAPBoard","system":"65b399e996f1fc092ed2079e","displayName":"IAPBoard","params":{"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"right":true,"top":true,"bottom":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1600,"height":1000}}},"tasks":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-board-tasksDataSet"}},"people":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-board-peopleDataSet"}},"comments":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-board-commentsDataSet"}},"fileRequests":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-board-file-requestsDataSet"}},"saveTask":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap_board_save_task"}},"deleteTask":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap_board_delete_task"}},"saveComment":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap_board_save_comment"}},"saveFileRequest":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap_board_save_file_request"}},"savePerson":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap_board_save_person"}},"drive":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-board-drive"}},"auth":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapAccountAuth"}},"knowledge":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-board-knowledgeDataSet"}},"saveKnowledge":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap_board_save_knowledge"}},"account":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-board-accountsDataSet"}}},"index":1,"reference":"JM4J5la0_RpAXJIc-dgtK"},{"_id":"b112ca0c6867d1c21f3c79dd","parent":"6a75c0cf8bf663d9684a81d2","component":"natura-x-app@kAuthGate","system":"65b399e996f1fc092ed2079e","displayName":"kAuthGate","params":{"verifier":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iap-board-accountsDataSet"}},"auth":{"component":"data2-core-types@InteractiveModuleReference","params":{"reference":"iapAccountAuth"}},"super":{"position":{"component":"data2-core-types@Position","params":{"x":0,"y":0,"freeTransform":true,"left":true,"top":true}},"dimensions":{"component":"data2-core-types@Dimensions","params":{"width":1,"height":1}}}},"index":2,"reference":"HvkS3mu3KYHrUtx4wlwxP"}]}},{"_id":"5fc4faeec8da12be8bf3d08a","component":"data2-core@Data2TableDataSource","system":"5e3b796d6228227ca637d0f4","params":{"paginate":{"default":1000},"super":{},"table":"fileComponentInstances","schema":[{"component":"data2-core-types@Field","params":{"key":"component","title":"Component","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"system","title":"System","component":"data2-core-primitives@ObjectId"}},{"component":"data2-core-types@Field","params":{"key":"displayName","title":"Display Name","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":"params","title":"Params","component":"data2-core-primitives@Object"}},{"component":"data2-core-types@Field","params":{"key":"reference","title":"Reference","component":"data2-core-primitives@String"}},{"component":"data2-core-types@Field","params":{"key":{"component":"data2-core-primitives@String","params":{"content":"fileId"}},"title":"File ID","component":"data2-core-primitives@ObjectId"},"break":true}],"hooks":{"before":{"all":[{"hook":"softDelete"}],"find":[{"hook":"useCache","options":{"additionalGroupKeys":["$and.0.system","$and.1.system"],"skipCache":true}}],"create":[{"hook":"timestamp","options":{"fields":["createdAt","updatedAt"]}}],"update":[{"hook":"timestamp","options":{"fields":["updatedAt"]}}],"patch":[{"hook":"timestamp","options":{"fields":["updatedAt"]}}]},"after":{"all":[{"hook":"fileUrl","options":{"downloadField":"params.fileUrl","uploadField":"params.uploadFileUrl","enhanceSchema":false}}],"find":[{"hook":"useCache","options":{"additionalGroupKeys":["$and.0.system","$and.1.system"],"skipCache":true}}],"create":[{"hook":"invalidateCache","options":{"additionalGroupKeys":["system"],"secondaryUniqueKeys":["reference"]}},{"hook":"invalidateEnvCache"}],"update":[{"hook":"invalidateCache","options":{"additionalGroupKeys":["system"],"secondaryUniqueKeys":["reference"]}},{"hook":"invalidateEnvCache"}],"patch":[{"hook":"invalidateCache","options":{"additionalGroupKeys":["system"],"secondaryUniqueKeys":["reference"]}},{"hook":"invalidateEnvCache"}],"remove":[{"hook":"invalidateCache","options":{"additionalGroupKeys":["system"],"secondaryUniqueKeys":["reference"]}},{"hook":"invalidateEnvCache"}]}},"multi":["create"],"useEstimatedDocumentCount":true},"reference":"FileComponentInstancesTableDataSource","updatedAt":"2022-12-19T20:42:51.837Z","createdAt":"2020-11-30T13:59:52.294Z"},{"reference":"ApplicationsTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[{"_id":"6a75c61e91fcc12ded029fe7","displayName":"Painel de Gestão — Experimento IAP","reference":"ze87qx6nH-Qqgq4bq2sgU","params":{"name":{"component":"data2-core-primitives@String","params":{"content":"Painel de Gestão — Experimento IAP"}},"shortName":{"component":"data2-core-primitives@String","params":{"content":"Painel IAP"}},"domain":{"component":"data2-core-primitives@String","params":{"content":"painel-management-iap"}},"serverSideRendering":{"component":"data2-core-primitives@Boolean","params":{"content":true}},"entryPoint":{"component":"data2-core-types@ComponentInstanceReference","params":{"component":"data2-core-primitives@Artboard","reference":"jdjrJRm81oW3RyzM7flgj"}},"responseHeaders":{"X-Frame-Options":{"component":"data2-core-primitives@String","params":{"content":""}},"Content-Security-Policy":{"component":"data2-core-primitives@String","params":{"content":""}},"X-XSS-Protection":{"component":"data2-core-primitives@String","params":{"content":""}}}},"system":"65b399e996f1fc092ed2079e","component":"data2-core-primitives@Application"}]}},{"reference":"BundlesTableDataSource","component":"data2-core@MemoryDataSource","params":{"initialData":[]}}],"processesParams":{}}}}